SpringBoot两天

news2024/12/27 9:26:44

SpringBoot讲义

什么是SpringBoot?

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

Spring 从4.x版本开始提倡java配置和注解结合使用,慢慢离开xml繁琐的配置,所以要入门SpringBoot,需要从java配置开始。在Spirng Boot和Spring Cloud中,大量使用了注解与JavaConfig。

JavaConfig诞生的原因是什么?

Spring IOC有一个非常核心的概念——Bean。由Spring容器来负责对Bean的实例化,装配和管理。早些时候XML是用来描述Bean最为流行的配置方式。随着Spring的日益发展,由于Spring会把几乎所有的业务类都以Bean的形式配置在XML文件中,造成了大量的XML文件。使用XML来配置Bean失去了编译时的类型安全检查。大量的XML配置使得整个项目变得更加复杂。

当随着JAVA EE 5.0的发布,其中引入了一个非常重要的特性——Annotations(注解)。注解是源代码的标签,这些标签可以在源代码层进行处理或通过编译器把它熔入到class文件中。在JAVA EE 5以后的版本中,注解成为了一个主要的配置选项。Spring使用注释来描述Bean的配置与采用XML相比,因类注解是在一个类源代码中,可以获得类型安全检查的好处。可以良好的支持重构。

JavaConfig就是使用注解来描述Bean配置的组件。JavaConfig 是Spring的一个子项目。

JavaConfig常见的注解有哪些?

@Configuration注解

该注解贴在类上,表示是一个配置类。相当于xml配置

@Bean注解

Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里。添加的bean的id为方法名

也可以执行生命周期的回调函数,类似@PostConstruct(初始化方法) 和 @PreDestroy(销毁方法)的方法

@Scope注解

@Scope 注解:用来设置交给spring容器管理的Bean是单例方式,还是多例方式
相当于bean标签中的scope属性

@ComponentScan注解

创建一个配置类,在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的xml <context:component-scan>。

@Import注解

@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名

@ImportResource注解

@ImportResource和@Value进行资源文件读取

@PropertySource, @value 注解

Spring框架提供了PropertySource注解,目的是加载指定的属性文件 @Value 可以获取配置文件中的值

@Profile,@ActiveProfile注解

@profile注解是spring提供的一个用来标明当前运行环境的注解。我们正常开发的过程中经常遇到的问题是,开发环境是一套环境,QA测试是一套环境,线上部署又是一套环境。这样从开发到测试再到部署,会对程序中的配置修改多次,尤其是从QA到上线这个环节,让QA的也不敢保证改了哪个配置之后能不能在线上运行。 为了解决上面的问题,我们一般会使用一种方法,就是配置文件,然后通过不同的环境读取不同的配置文件,从而在不同的场景中跑我们的程序。

那么,spring中的@profile注解的作用就体现在这里。在spring使用DI来依赖注入的时候,能够根据当前制定的运行环境来注入相应的bean。最常见的就是使用不同的DataSource了。

JavaConfig配置的应用场景

在IOC中

需求一:把一个Bean交给Spring管理

回顾使用xml配置方式

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="someBean" class="cn.wolfcode.javaconfig._01_xmlconfig.SomeBean"/>
</beans>

SomeBean:

public class SomeBean {
    public void print() {
        System.out.println("print xmlConfig");
    }
}

XMLConfigTest:

public class XMLConfigTest {
    
    @Test
    public void testXMLConfig() throws Exception {
        // 加载spring的配置文件
        ApplicationContext ctx =
                new ClassPathXmlApplicationContext("cn/wolfcode/javaconfig/_01_xmlconfig/applicationContext.xml");

        // 通过spring容器获取SomeBean
        SomeBean someBean = ctx.getBean(SomeBean.class);

        // 执行print方法
        someBean.print();
    }

}
使用javaConfig方式

@Configuration :spring的配置标签,打在哪个类上,这个类就表示spring的配置类, 相当于applicationContext.xml文件

@Bean :打在方法上方法返回的实例对象交给spring容器管理,方法的名称就是Bean标签中的id值。
相当于配置文件中bean标签。
如:

<bean  id="someBean" class="cn.wolfcode._01xmlconfig.SomeBean" />

ApplicationConfig:

@Configuration
public class ApplicationConfig {

    @Bean
    public SomeBean someBean(){
        return new SomeBean();
    }
}

SomeBean:

public class SomeBean
{
    public void print(){
        System.out.println("print JavaConfig");
    }
}

这里不再是通过xml配置文件获取spring容器对象,而是通过加载配置类。因此应该选用 AnnotationConfigApplicationContext 类,加载配置类的字节码文件。

JavaConfigTest:

public class JavaConfigTest {
    @Test
    public void testJavaConfig() throws Exception {

        //  加载配置类
        ApplicationContext ctx =
                new AnnotationConfigApplicationContext(ApplicationConfig.class);
        // 从spring容器中获取SomeBean对象
        SomeBean someBean = ctx.getBean(SomeBean.class);
        // 打印
        someBean.print();
    }
}

@ComponentScan

使用该注解改良代码。

通过上述的案例,其实我们发现,上面的写法也显得很麻烦。还不如使用配置文件简洁。
在配置类中,通过给方法添加注解@Bean,把该方法的返回值交给spring管理。代码量太大,如果需要交给Spring管理很多Bean的话,我们要写很多个方法,在配置类中。

在javaConfig中,给我们提供一个注解@ComponentScan,可以把需要管理的Bean 贴上注解@Component,然后通过在配置类上面贴注解@ComponentScan来进行扫描贴了注解的Bean,然后把这些Bean交给Spring管理。

@ComponentScan 这个标签也相当于之前在配置文件中的配置的

<context:component-scan base-package="" />  
//开启 ioc组件自动扫描功能

注意:@ComponentScan注解也可以通过配置指定扫描哪个包。

    如:@ComponentScan("cn.wolfcode")
    表示扫描“cn.wolfcode”这个包以及它下面的子包,把包中的贴了注解的Bean ,交给spring容器管理。
    如果不指定包,表示扫描当前包以及下面的子包

改良方案如下:

ApplicationConfig:

@Configuration
@ComponentScan("cn.wolfcode.javaconfig._03_scan")
public class ApplicationConfig {

}

JavaConfigTest:

public class JavaConfigTest {
    @Test
    public void testJavaConfig() throws Exception {
        //  加载配置类
        ApplicationContext ctx =
                new AnnotationConfigApplicationContext(ApplicationConfig.class);
        // 从spring容器中获取SomeBean对象
        SomeBean someBean = ctx.getBean(SomeBean.class);
        // 打印
        someBean.print();
    }
}

SomeBean:

@Component
public class SomeBean
{
    public void print(){
        System.out.println("print ScanConfig");
    }
}

@Bean中的一些属性的补充

属性:initMethod

该属性相当于bean标签中的initMethod属性,用来设置bean被初始化所调用的方法。
如:

<bean id="someBean" class="" init-method="init" />

属性:destroyMethod

该属性相当于bean标签中的destroyMethod 属性,用来设置bean被销毁时,执行的操作。
如:

<bean id="someBean" class=""  destroy-method=""   />

注意:必须是容器正常关闭,销毁bean,才会调用销毁方法。

使用普通的junit测试,是不能关闭spring容器的。原因是,junit测试方法执行完毕,进程就结束了。所以导致spring容器不能正常关闭。

正常关闭容器的方案:

方案一: 容器使用完手动调用close方法,进行关闭。

如:

//获取容器
AnnotationConfigApplicationContext ctx =  new AnnotationConfigApplicationContext(ApplicationConfig.class)
// 关闭容器
ctx.close();

方案二:使用java1.7的新特性try(),来进行关闭。

如:

try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationConfig.class)) {
    SomeBean someBean = ctx.getBean(SomeBean.class);
    System.out.println(someBean);
} catch (Exception e) {
    e.printStackTrace();
}

方案三:使用Spring整合的junit单元测试。

如:

在测试类上面添加整合单元测试的注解. 
注意:因为不再是加载配置文件的方式,所以需要使用classes属性加载配置类信息。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)

@Scope注解

@Scope 该注解贴方法上表示是多例还是单例, 相当于配置文件中bean标签的属性scope 的作用。

@Scope("prototype") 表示多例
@Scope("singleton") 表示单例,如果配置@Scope标签,其默认值也是单例。

一般开发中中使用的是单例。

在DI中

需求二:把一个Bean注入给另外一个Bean中

方式一:直接调用方法的形式
 @Bean
 public SomeBean someBean() {
    SomeBean someBean = new SomeBean();
    System.out.println("someBean create");
    someBean.setOtherBean(otherBean());
    return someBean;
  }
  
 @Bean
 public OtherBean otherBean() {
    OtherBean otherBean = new OtherBean();
    System.out.println("otherBean create");
    return otherBean;
 }
方式二:使用传参的形式
@Bean
public SomeBean someBean(OtherBean otherBean){
   someBean.setOtherBean(otherBean);
    return someBean;
}

@Bean
public OtherBean otherBean(){
    return new OtherBean();
}

注意:

@Bean标签的生命周期
在一个@Configuration类中,重复调用一个方法,得到的对象默认也是singleton的

配置类/文件的导入

为何需要导入配置类/配置文件?

在我们实际开发中,项目很大的情况下,采用JavaConfig配置类的方式,为了便于开发和管理,我们的配置类也会有很多个。那么多个配置类之间,只有一个主配置类,这时就需要,把其他的配置类或者配置文件导入到主配置类上面。所以我们需要导入配置类或者配置文件。

导入配置类/配置文件需要哪些注解?

@Import注解

比如一个场景:我们之前项目中可以有多个 配置文件,那么在javaConfig中,也可能存在多个配置类,那么在很多个配置类的情况下,应该有一个主配置类,其他的配置都关联到主配置类上面,那么问题是怎么关联其他配置类呢? 使用@Import注解

@ImportResource注解

比如一个场景:有时候根据项目的需求和对项目的设计,我们可能需要配置类和配置文件混用的情况。例如,使用spring的AOP帮我们处理事务。可能使用javaConfig的方式,比较繁琐,修改代码太过于频繁,那么这块我们就可以继续使用xml文件的方式统一处理。那么这种情形就需要,在主配置类中关联配置文件了。

@Import注解的用法:

导入普通类

例子如下:

    1. 新建一个TestA
public class TestA {
    public void fun(String str) {
        System.out.println(str);
} 
public void printName() {
    System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
    }
}
    1. 新建一个ImportConfig,在类上面加上@Configuration,加上@Configuration是为了能让Spring 扫描到这个类,并且直接通过@Import引入TestA类
@Import({TestA.class})
@Configuration
public class ImportConfig {
}

导入带有@Configuration的配置类

    1. 新建TestB
@Configuration
public class TestB {
    public void fun(String str) {
        System.out.println(str);
    }
    public void printName() {
        System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
    }
}

    1. 在ImportConfig.class里面直接引入TestB
@Import({TestA.class,TestB.class})
@Configuration
public class ImportConfig {
} 

通过ImportSelector 方式导入的类

    1. 新建TestC.class
public class TestC {
  public void fun(String str) {
        System.out.println(str);
  }
  public void printName() {
    System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
  }
}
    1. 新建SelfImportSelector.class 实现ImportSelector 接口,注入TestC.class
public class SelfImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.test.importdemo.TestC"};
    }
}
    1. ImportConfig上面引入SelfImportSelector.class
@Import({TestA.class,TestB.class,SelfImportSelector.class})
@Configuration
public class ImportConfig {

}

通过 ImportBeanDefinitionRegistrar 方式导入的类

    1. 新建TestD.class
public class TestD {
    public void fun(String str) {
         System.out.println(str);
   } 
   public void printName() {
      System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
   }
}
    1. 新建SelfImportBeanDefinitionRegistrar.class,实现接口ImportBeanDefinitionRegistrar,注入TestD.class
public class SelfImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition root = new RootBeanDefinition(TestD.class);
        registry.registerBeanDefinition("testD", root);
    }
}
    1. ImportConfig类上加上导入SelfImportBeanDefinitionRegistrar.class
@Import({TestA.class,TestB.class,SelfImportSelector.class,
    SelfImportBeanDefinitionRegistrar.class})
@Configuration
public class ImportConfig {
}

@ImportResource注解的用法:

有时候根据项目的需求和对项目的设计,我们可能需要配置类和配置文件混用的情况。
比如,使用spring的AOP帮我们处理事务。可能使用javaConfig的方式,比较繁琐,修改代码太过于频繁,那么这块我们就可以继续使用xml文件的方式统一处理。那么这种情形就需要,在主配置类中关联配置文件了。
使用@ImportResource注解
是引入spring配置文件.xml,实现xml配置的装载;
通过locations属性或者value属性,加载对应的xml配置文件,同时需要配合@Configuration注解一起使用,定义为配置类;引入的配置类必须在启动类中扫描到.

例子如下:

ApplicationConfig:

@Configuration
@ImportResource("classpath:cn/wolfcode/javaconfig/_07_import_resource/applicationContext.xml")
public class ApplicationConfig {
    @Bean
    public SomeBean someBean(){
        return new SomeBean();
    }
}

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="cn.wolfcode.javaconfig._07_import_resource.OtherBean"/>
</beans>

ImportResourceConfigTest:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)
public class ImportResourceConfigTest {
    @Autowired
    private SomeBean someBean;
    @Autowired
    private OtherBean otherBean;
    @Test
    public void testImportResource() throws Exception {
        System.out.println("someBean = " + someBean);
        System.out.println("otherBean = " + otherBean);
    }
}

资源文件的导入

为何需要导入资源文件?

Spring Boot 官网使用的是application.properties文件来实现文件的配置。但是实际情况下一个配置文件是不够用的,比如项目集成redis,mq,以及数据库比如mysql的时候,多个配置文件有利于开发及维护的管理。

导入资源文件需要学习哪些注解?

@PropertySource注解

大多数情况,项目需要配置的内容会比较复杂,这时候需要使用多个文件进行配置
@PropertySource是Spring boot为了方便引入properties配置文件提供的一个注解,可以标注在SpringBoot的启动类上,还可以标注在配置类(使用@Configuration标注的类)上。

@Value注解

@Value用于将外部的值动态地注入Bean中,相当于使用.xml中的如下的代码。通常用于对某个值进行单个的注入,毕竟如果对整个Bean内的值进行注入我们有更好用的@ConfigurtionProperties可以使用。

@ConfigurtionProperties注解

@Value通常用于对某个位置获取配置文件中的的某一个变量的时候,而@ConfigurationProperties用于javaBean和配置文件进行整体映射。上述的使用场景只是说通常情况,具体的使用,还是要根据@Value和@ConfigurationProperties各自的特点来进行判断。

@Value主要作用到变量上,而@ConfigurationProperties,主要用于方法。

@PropertySource注解的用法:

加载指定的属性文件;
注解的属性value,是一个String数组,内容为一个或多个xml/properties文件的路径(相对于classpath);
注解内部的属性使用,配合@value注解.
properties属性文件是key-value格式的,因此使用起来 是采用@value("${key}"); 这种形式

如:

@Configuration
@PropertySource(value = "classpath:db.properties",
ignoreResourceNotFound = false ,
encoding = "utf-8")
public class ApplicationConfig {
}

属性value

value值是设置需要加载的属性文件,可以一次性加载多个。

属性encoding

encoding用于指定读取属性文件所使用的编码,我们通常使用的是UTF-8;

属性ignoreResourceNotFound

ignoreResourceNotFound含义是当指定的配置文件不存在是否报错,默认是false;实际项目开发中,最好设置为false。

Environment对象的使用

回想mybaits配置文件中的 Environment ,表示mybatis的当前环境
Environment :代表了spring的当前环境,在该环境中只有两个东西

1,外部读入的资源文件 :如通过PropertySource 引入的资源文件

2,还有一个东西叫 profile

那么在javaconfig中如何使用 Environment 实现资源文件的注入呢 ?

@Autowired
private Environment evn;

@Configuration
//@PropertySource:把资源文件导入到Spring当前环境
@PropertySource("classpath:jdbc.properties")
public class ApplicationConfig
{
// Environment spring的环境对象,其中有两个东西:① propertis ② profile
 @Bean
public MyDataSource myDataSource(Environment environment){
    return new MyDataSource(environment.getProperty("jdbc.username")
                ,environment.getProperty("jdbc.password")
                ,environment.getProperty("jdbc.driverClassName")
                ,environment.getProperty("jdbc.url"));
        }
}

@Value注解的用法:

我们可以点进去看一下@Value的源码,如下,我们发现它其中只包括一个String类型的变量,也就是说我们@Value注解后面只能接一个String类型的变量,无论什么内容都必须包括在双引号之下。

可注入类型:

    1. 字面量:包括布尔、数值型、字符串
  • a. 字符串
@Value("字符串内容")
适用于一般的自变量、数组、文件资源和URL资源,Spring Boot会根据配置的变量类型对字符串内容进行转换。
例如:
@Value("true")
private Boolean blog;

  • b. SpEL
@Value("#{SpEL内容}")。
通常SpEL内的字符串使用单引号''括起来。适用于需要计算的自变量、特殊数组、键值对、其他bean属性等。
例如:
    @Value("#{12*15}")
private Integer clickNum;

  • c. 注入配置文件中的数据
@Configuration
@PropertySource("classpath:jdbc.properties")
public class ApplicationConfig
{
    //取资源文件中的内容,对于的值设置到该字段上
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.driverClassName}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.password}")
    private String password;

   @Bean
  public MyDataSource myDataSource(){
      return new MyDataSource(username,password,driverClassName,url);
}

注意:

如果spring的jar版本过低,必须配置PropertySourcesPlaceholderConfigurer
PropertySourcesPlaceholderConfigurer ,完成资源文件属性到对象参数的绑定,从spring环境对象中拿到资源文件配置,完成对 {key} 表达式的替换 ,加static保证该类在其他所有bean之前初始化,因为该类要在其他类初始化之前完成{key} 表达式的替换

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
        return new PropertySourcesPlaceholderConfigurer();
    }
}

PropertySourcesPlaceholderConfigurer 类最终实现了一个 BeanFactoryPostProcessor 接口 ,他的作用是在BeanFactory创建每一个bean的过程当中加入一些自己的业务逻辑

而实现了BeanFactoryPostProcessor 接口的bean 会在其他所有bean 初始化之前去做一些事情 , 而 PropertySourcesPlaceholderConfigurer 就是在其他所有的bean初始化之前去寻找是否有 value 标签, 如果有完成属性的替换, 所以我们得需要保证 PropertySourcesPlaceholderConfigurer 在其他所有类之前被创建,所以需要在 该bean的定义方法是 static

    1. 数组(List)

字符串:与字面量类似,字符串之间使用英文的“,”隔开。
如:

@Value("Spring Boot, IDEA")
private List<String> key;

SpEL:比如我们需要字符串中有逗号的时候,使用SpEL就会更合适一些。
如:

@Value("#{'Spring Boot;IDEA'.split(';')}")
private List<String> key;
    1. 键值对(Map)

对于键值对我们不能直接使用字符串,可以使用SpEL,也可以配合配置文件来进行使用。

a. SpEL,注意第二层的括号一定要有:

@Value("#{{color:'red', language:'Chinese'}}")
private Map<String,String> otherProperties;

b. 如果结合配置文件使用,我们这里使用.properties格式和.yml格式的配置的方式有些不同,先来看.properties配置文件代码:

    valuesMap={color:'red', language:'Chinese'}
    代码中获取:
    @Value("#{${valuesMap}}")
    private Map<String,String> otherProperties;

如果配置文件使用yml,需要注意。
yml在进行单行键值对赋值的时候使用{},所以需要加上双引号"",因为单引号会将字符串内容转义,所以我们这里使用双引号。.yml配置文件代码:

valuesMap: "{color:'red', language:'Chinese'}"
代码中获取:
@Value("#{${valuesMap}}")
private Map<String,String> otherProperties;
    1. 注入其它bean属性

otherBean是一个Bean对象,name是它的一个属性,可以通过@Value(“#{otherBean.name}”)将这个bean的属性注入@Value声明的属性里

@Value("#{otherBean.name}")
private String someName;
    1. URL资源

与字面量中的字符串类型变量的注入差不多,支持多种形式,我们只示例字符串的形式:
如:

@Value("http://www.baidu.com")
private Resource address;
    1. 操作系统属性

操作系统的属性是静态全局变量systemEnvironment存入,可通过它获取到操作系统的属性。

@Value("#{systemProperties['os.name']}")
private String name;
    1. 文件资源

与字面量中的字符串类型变量的注入差不多,支持多种形式,我们只示例一种。注意这里我们一定要在文件路径前面加上classpath,并且将路径从文件夹处复制过来的时候,最好更改格式,把里面的“\”和“\”改为“/”。当然你也可以不改,Spring Boot也能够识别:
如:

 @Value("classpath:D:/code/application/a.txt")
 private Resource intro;

开发环境的切换

为何需要学习@Profile注解?

由于我们平时在开发中,通常会出现在开发的时候使用一个开发数据库,测试的时候使用一个测试的数据库,而实际部署的时候需要一个数据库。以前的做法是将这些信息写在一个配置文件中,当我把代码部署到测试的环境中,将配置文件改成测试环境;当测试完成,项目需要部署到现在了,又要将配置信息改成现在的。非常麻烦。

而使用了Profile之后,我们就可以分别定义3个配置文件,一个用于开发、一个用户测试、一个用户生产,其分别对应于3个Profile。当在实际运行的时候,只需给定一个参数来激活对应的Profile即可,那么容器就会只加载激活后的配置文件,这样就可以大大省去我们修改配置信息而带来的烦恼。

@Profile/@ActiveProfile注解

spring3.1开始引入,一般用于多环境配置
@Profile注解用于实现通过修改运行时参数,切换不同的开发环境

@Profile注解可以加在类上,也可以加载注入bean的方法上

@Profile只有一个默认的java.lang.String[]类型的属性value,因此它可以指定一个或多个配置名。

@Profile注解,匹配的名字才会去处理,没有匹配的则会被抛弃。

@Profile标签能够定义不同的运行环境,在测试的时候,可以通过

@ActiveProfile来指定运行环境

如何使用两个注解完成环境的切换

1.png

开发环境配置:

@Configuration
@PropertySource("classpath:dev.properties")
@Profile("dev")
public class ApplicationDevConfig
{    
    @Bean
public MyDataSource myDataSource(Environment environment){
    return new MyDataSource(environment.getProperty("jdbc.username")  ,environment.getProperty("jdbc.password")          ,environment.getProperty("jdbc.driverClassName")      ,environment.getProperty("jdbc.url"));

    }
}

测试环境配置:


@Configuration
@PropertySource("classpath:test.properties")
@Profile("test")
public class ApplicationTestConfig
{
    @Bean
 public MyDataSource myDataSource(Environment environment){
        return new MyDataSource(environment.getProperty("jdbc.username")            ,environment.getProperty("jdbc.password")
              ,environment.getProperty("jdbc.driverClassName")
                ,environment.getProperty("jdbc.url"));
 }
}

主配置:

@Configuration
@Import({ApplicationDevConfig.class,ApplicationTestConfig.class})
public class ApplicationConfig
{
}

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)
@ActiveProfiles("test")
public class JavaConfigTest {
    @Autowired
    private MyDataSource myDataSource;
    @Test
    public void someBeanTest(){   
    System.out.println(myDataSource.toString());
    }
}

HelloWorld案例

第一个helloWorld案例:

pom.xml:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
    <relativePath/>
</parent>

 <!--整合web-->
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
        

HelloWordController:

@Controller
@SpringBootApplication 
public class HelloWordController {
    
    @RequestMapping("/helloboot")
    @ResponseBody
    public String hello() {
        System.out.println("hello springBoot");
        return "hello springBoot";
    }

    public static void main(String[] args) {
        SpringApplication.run(HelloWordController.class,args);
    }
}

运行原理分析:

1,使用 <parent> 继承spring-boot-starter-parent,引入基本的依赖管理配置;

2,引入spring-boot-starter-web,自动引入了springweb相关的包;


@SpringBootApplication 标签做了哪些事情 ?

@SpringBootApplication由三个主要的标签构成:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan

1)@SpringBootConfiguration:本质就是一个@Configuration,代表这是spring容器的主配置类;

2)@EnableAutoConfiguration:开启自动配置,Springboot使用这个标签自动的把内置的符合条件的@Configuration类加载进入应用;

可以查看spring-boot-autoconfigure包中的META-INF/spring.factories文件中的配置项(原理,由@EnableAutoConfiguration标签引入的AutoConfigurationImportSelector类中,使用Spring的SpringFactoriesLoader类实现加载)

3)@ComponentScan:自动扫描;

总结一句话:

@SpringBootApplication:这个注解告诉springboot自动的去完成相关配置,包括基础类的加载,bean的扫描等等,这个后面详细介绍;简单理解为这个标签为我们的应用配置完成了很多基本功能;


SpringApplication.run 又做了哪些事情?

读取配置对象,配置相关自动配置对象,启动tomcat加载应用

总结一句话:

SpringApplication.run:这个是springboot为我们提供的最大的区别,在于springboot不再是一个web应用,需要我们自己去打包,部署,启动tomcat,springboot默认把tomcat打包到应用中,我们可以以正常的运行jar的方式来运行springboot应用;

springBoot项目独立运行方式:

注意:默认的Maven打包方式是不能正常的打包SpringBoot项目的,需要额外的引入打包插件,才能正常的对SpringBoot项目 打成jar包,以后只要拿到该jar包就能脱离IDE工具独立运行了
    1. 使用插件 springboot:run

添加插件

 <build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
 </build>
 
对于eclipse 项目要打包,引入插件之后  直接用 package 命令、
对于idea直接点击打包插件即可
    1. 打包独立运行

执行java -jar xxx.jar 运行(在字节码编译目录中会有打包好的 jar)

    1. 使用main方法启动

方式一:直接调用run方法

    SpringApplication.run(StartModeController.class,args);

方式二:可以通过创建对象的方式来运行SpringApplication

    SpringApplication application = new SpringApplication(StartModeController.class); 
    application.run(args);

方式三:通过builder完成:

    SpringApplication application = new SpringApplicationBuilder(StartModeController.class).build();
    application.run(args);

Springboot的优缺点:

  1. 创建独立的Spring应用程序

  2. 嵌入的Tomcat,无需部署WAR文件

  3. 简化Maven配置

  4. 自动配置Spring

  5. 提供生产就绪型功能,如日志,健康检查和外部配置

  6. XML没有要求配置

  7. 非常容易和第三方框架集成起来;

缺点:

1,版本更新较快,可能出现较大变化;

2,因为约定大于配置,所以经常会出现一些很难解决的问题;

Springboot基本使用

应用构建

使用官网进行构建springboot项目

构建项目的地址:http://start.spring.io/

使用开发工具构建springboot项目

新建一个项目 ,选则 spring  Initializr 选项。
填写相关的信息。选则相关的jar依赖。

热部署

直接添加

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

即可

可以在application.properties文件中设置一些属性

#默认排除的资源 spring.devtools.restart.exclude=static/**,templates/**,public/** #增加额外的排除资源
spring.devtools.restart.additional-exclude=public/** #处理默认配置排除之外的 spring.devtools.restart.enabled=false #禁用自动重启

热部署的原理

SpringBoot重启是restart重启,通过监控classpath的变化,如果classpath中的文件发生变化,即出发重启。springboot通过两个classpath来完成reload,一个basic classloader中加载不变的类,一个restart classloader中加载classpath中的类,重启的时候,restart classloader中的类丢弃并重新加载,所以

设置自动编译:如果您觉得 SpringBoot的热部署不怎么好用,那你也可以去掉热部署的依赖,使用自动编译:

1,开启自动编译 :setting -> Build... -> compiler -> Build project automatically

2,ctrl + alt + shift +/ -> registry -> compiler.automake.allow.when.app.running

设置banner

1,可以直接在classpath下添加一个banner.txt文件即可;

2,介绍springboot的配置文件。springboot默认在classpath中通过application.properties文件来进行配置;比如:
spring.main.banner-mode=off
就可以关闭banner输出

SpringApplication的参数

可以通过注入一个ApplicationArguments对象来完成参数的封装;

@Autowired
private ApplicationArguments args;
    @RequestMapping("/args")
    public String args() {
        return args.getNonOptionArgs().toString();
}

Springboot开发WEB应用

集成日志框架

为什么要用日志?
1,比起System.out.println,日志框架可以把日志的输出和代码分离;
2,日志框架可以方便的定义日志的输出环境,控制台,文件,数据库;
3,日志框架可以方便的定义日志的输出格式和输出级别;
java常用日志框架类别介绍
    1. Log4j

Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。 Log4j是几种Java日志框架之一。

    1. Log4j 2

Apache Log4j 2是apache开发的一款Log4j的升级产品。

    1. Commons Logging

Apache基金会所属的项目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更名为Commons Logging。

    1. Slf4j

类似于Commons Logging,是一套简易Java日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写Slf4j)。

    1. Logback

一套日志组件的实现(slf4j阵营)。

Java常用日志框架历史

1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j。后来Log4j成为Apache基金会项目中的一员。

期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议sun引入Log4j到java的标准库中,但Sun拒绝了。

2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。在JUL出来以前,log4j就已经成为一项成熟的技术,使得log4j在选择上占据了一定的优势。

接着,Apache推出了Jakarta Commons Logging,JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是log4j,也可以是Java Util Logging。

后来(2006年),Ceki Gülcü不适应Apache的工作方式,离开了Apache。然后先后创建了slf4j(日志门面接口,类似于Commons Logging)和Logback(Slf4j的实现)两个项目,并回瑞典创建了QOS公司,QOS官网上是这样描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。

Apache眼看有被Logback反超的势头,于2012-07重写了log4j 1.x,成立了新的项目Log4j 2。Log4j 2具有logback的所有特性。Log4j2与Log4j1发生了很大的变化,log4j2不兼容log4j1。

Springboot的默认日志使用

1,Springboot默认已经开启日志;默认的日志格式为:时间 日志级别 PID 线程名称 日志类 日志说明

2,Springboot的日志区别系统日志和应用日志;

3,Springboot推荐使用Logback作为日志框架

Logback使用方法(推荐使用logback自己的配置文件)

1,springboot默认支持logback.xml或者logback-spring.xml,推荐使用logback-spring.xml,springboot会增加额外功能;

2,可以通过logging.config=classpath:mylogback.xml指定自己的logback配置文件(不推荐);

3,一个典型的logback配置文件:

Logback使用介绍:

使用日志框架的步骤:

a. 在resources文件夹下面创建一个xml文件,文件的名称使用 logback-spring.xml。

b. 在logback-spring.xml文件中编写日志文件的内容:

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!--日志的输出方式:  输出到控制台-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-%class:%line>>%msg%n</pattern>
        </encoder>
    </appender>
    
 <!--配置日志的类型以及输出日志的级别: 使用系统的日志, 日志级别为info-->
    <root level="info">
        <!--必须要和appender中的name属性的值保持一致-->
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>  

c.启动项目即可。

参考日志格式:
%d{yyyy-MM-dd-HH:mm:ss} %level [%thread]-%class:%line >> %msg %n
格式中的标识符组成:
%logger{n}: 输出Logger对象类名,n代表长度
%class{n}: 输出所在类名, 
%d{pattern}或者date{pattern}: 输出日志日期,格式同java  %L/line: 日志所在行号
%m/msg: 日志内容 
%method: 所在方法名称 
%p/level: 日志级别  
%thread: 所在线程名称

关于logback-spring.xml配置文件的详细解析:

1,configuration:Logback配置根元素

属性包括:

a,scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。

b,scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。

c,debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。

子元素:

<contextName>:上下文名字;
<property>:定义属性,可以使用${}在配置文件中使用;

2,appender:在logback中是用于具体记录日志的组件,可以用来配置日志记录方式,日志记录格式等;

属性包括:

name:appender的名字,用于下面在配置日志的时候指定;
class:使用到的appender类;

常见的appender:

1,ch.qos.logback.core.ConsoleAppender:输出到控制台;

<!--输出到控制台-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--日志格式化-->
    <encoder>
        <pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-%class:%line>>%msg%n</pattern>
    </encoder>  
</appender>

2,ch.qos.logback.core.FileAppender:输出到文件;

file:被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。 
append:文件结尾,如果是 false,清空现存文件,默认是true。 
encoder:对日志进行格式化 

<!--输出到文件中-->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-%class:%line>>%msg%n</pattern>
    </encoder>
    <append>false</append>
    <file>mylog.log</file>
</appender>

3,ch.qos.logback.core.rolling.RollingFileAppender:输出到文件,可以配置滚动策略,当日志达到某个条件之后分文件记录;

append:如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。
rollingPolicy:滚动策略,涉及文件移动和重命名。

常用滚动策略:

ch.qos.logback.core.rolling.TimeBasedRollingPolicy:按照时间控制来控制记录文件;

fileNamePattern:文件名称格式,以%d{pattern};
maxHistory: 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。(以文件最小时间为准)

<!--输出到文件,可以设置文件滚动(分割)条件
    按照时间分割-->
<appender name="RollingTimeFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-%class:%line>>%msg%n</pattern>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>mylog.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
</appender>

ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy:按照时间和大小控制记录文件;

fileNamePattern:文件名称格式,可以使用%i来控制索引编号;
maxFileSize:这是活动文件的大小,默认值是10MB
maxHistory:可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。(以文件最小时间为准)

<!--输出到文件,可以设置文件滚动(分割)条件
    按照文件大小分割-->
<appender name="RollingSizeFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-%class:%line>>%msg%n</pattern>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <fileNamePattern>mylog.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
        <maxFileSize>1kb</maxFileSize>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
</appender>

<logger>元素:用来设置某一个包或具体的某一个类的日志打印级别及对应的appender;

属性:

name: 用来指定受此loger约束的某一个包或者具体的某一个类;

子节点:

appender-ref:可以配置多个,标识这个appender将会添加到这个logger

如:

<!--使用自己的日志-->
<logger level="trace" name="cn.wolfcode.springboot">
    <appender-ref ref="STDOUT"/>
</logger>

<root>元素:特殊的logger,代表根配置,如果没有单独指定日志包层级,都默认使用root定义的日志输出级别;

属性:

level:指定日志级别;

如:

<!--使用系统的日志-->
<root level="info">
    <appender-ref ref="STDOUT"/>
</root>

在代码中可以使用自定义的Logger对象,输出日志信息。

实现的方式一:通过LoggerFactory工厂对象创建Logger对象

// 方式一:自己定义一个Logger对象
    private static final Logger LOGGER = LoggerFactory.getLogger(LogController.class);

    @RequestMapping("/log")
    @ResponseBody
    public String log() {
        // 方式一的出生日志的方式
        LOGGER.info("方式一:我是info");
        LOGGER.debug("方式一:我是debug");
        LOGGER.error("方式一:我是error");
        return "log";
    }


实现的方式二:使用注解的方式

@Slf4j // 方式二:使用注解来输出日志,前提是需要依赖lombok包
public class LogController {

    @RequestMapping("/log")
    @ResponseBody
    public String log() {
        // 方式二的输出方式
        log.trace("方式二: trace信息");
        log.debug("方式二: debug信息");
        log.info("方式二:  info{}信息", 33333);
        log.error("方式二: error信息");
        return "log";
    }

SpringBoot项目的目录结构

我们通过idea创建出来的SpringBoot项目,会生成一个标准的项目的目录结构。

2.png

application.properties优先级

一个项目中可以有多个application.properties文件存放在不同目录中,此时他们会遵循固定的优先级来处理有冲突的属性配置

  1. 项目/config/application.properties

  2. 项目/application.properties

  3. classpath:config/application.properties

  4. classpath:application.properties

一般都在classpath:application.properties做配置,其他方式不使用

静态资源

  1. 默认情况下,Springboot会从classpath下的 /static , /public , /resources , /META-INF/resources下加载静态资源

  2. 可以在application.properties中配置spring.resources.staticLocations属性来修改静态资源加载地址

spring.resources.static-locations=classpath:/hello
注意设置完以后,会覆盖之前的设置。

  1. 因为应用是打成jar包,所以之前的src/main/webapp就作废了,如果有文件上传,那么就的必须去配置图片所在的路径
spring.resources.static-locations=classpath:/static,file:///Users/tony/Desktop/image/
    

集成Freemarker

为何要集成Freemarker

1.性能。velocity应该是最好的,其次是jsp,普通的页面freemarker性能最差(虽然只是几毫秒到十几毫秒的差距)。但是在复杂页面上(包含大量判断、日期金额格式化)的页面上,freemarker的性能比使用tag和el的jsp好。

2、宏定义比jsp tag方便

3、内置大量常用功能。比如html过滤,日期金额格式化等等,使用非常方便

4、支持jsp标签

5、可以实现严格的mvc分离

6、易学易用

我是看了一天文档就用得挺熟练了,freemarker文档写得太好了,例子丰富,照做一遍全都会了。

7、功能强大
比Velocity强大多了,还支持JSP Tag。不过最有意义的是macro功能,可以自定义常用的macro,实现页面常规操作的可复用性。

8、报错信息友好
很多应用服务器的JSP报错信息是无法定位到源代码行的。不过freemarker报错定位很准确,丝毫不差,而且信息丰富,一看就知道怎么回事(虽然那个黄黄的页面看起来让人难受)

与JSP相比,FreeMarker的一个优点在于不能轻易突破模板语言开始编写Java代码,因此降低了领域逻辑漏进视图层的危险几率。但缺点是需要一点附加配置来将其平稳地集成到应用程序中,一些IDE(集成开发环境)可能并不完全支持它,当然还有开发者或设计者也许需要学习一门陌生的模板语言。相关的JAR文件将要添加到WEB-INF/lib(在需要的时候,它们包含在Spring中) 。

Freemarker的一些常见语法回顾

获取当前日期,必须要格式化显示

<input value="${.now?string('yyyy-MM-dd')}"/>

获取Controller控制器中共享给页面的对象中的普通属性

<input id="${user.name}" name="${user.name}" value="${user.name}"/> 

获取Controller控制器中共享给页面的对象中的日期类型属性,必须要格式化显示

<input value="${(user.birthday?string('yyyy-MM-dd'))!}"/>

获取Controller控制器中共享给页面的对象中的boolean类型属性,必须要处理显示

<input value="${user.active?string("是","否")}"> 

对null 或者不存在的对象进行取值,可以设置默认值

例:${var!'我是默认值'}
即,有值时显示正常值,无值时显示默认值 

如果需要判断,使用if标签判断

<#--判断是否小于25-->
<#if user.age < 25 >
    young
<#else>
    old
</#if>
<br/>
<#--判断是否大于25-->
<#if user.age &gt; 25 >
   old
<#else>
  young
</#if>  

如果需要判断,可以使用switch标签判断

    <#switch user.age>
      <#case 30>
         very old
         <#break >
      <#case 25>
         old
         <#break >
      <#default >
         young
        <#break >
   </#switch> 

获取Controller控制器中共享给页面的list集合,必须要遍历进行显示

<#list userList as person>
    <tr>
     <td>${person.name}</td>
     <td>${(person.age>25) ?string("old","young")}</td>
     <td>${(person.birthday?string('yyyy-MM-dd hh:mm:ss'))!}</td>
  <td>${person.active?string("是","否")}</td>
   </tr>
</#list>  

userList 这个名字必须和controller控制器中的共享的key保持一致。

获取Controller控制器中共享给页面的map集合,必须要遍历进行显示

<#list map?keys as key>
    ${key}:${map[key]}<br/>
</#list>

map 这个名字必须和controller控制器中的共享的key保持一致。

自定义变量

<#assign num = 100 />  

自定义的变量参与计算

<font color="red"> ${num * 10} </font><br> 

处理list集合

first: 取List值第一个值<br/>
<span>${userList?first}</span>

last: 取List值最后一个值<br/>
<span>${userList?last}</span>

size: 集合大小<br/>
<span>${userList?size}</span>

sort: 对集合进行排序<br/>
<#list userList?sort as item>
<span>${item.name}</span>
</#list>
<br/>
如何集成Freemarker
    1. 导入相关的依赖:
<!--整合freemarker-->
  <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-freemarker</artifactId>
  </dependency>
    1. 在resources/templates目录下面创建对应的freemarker模板。

freemarker.ftl:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<table>
    <tr>
       <th>姓名</th>
       <th>年龄</th>
       <th>备注</th>
       <th>生日</th>
       <th>是否激活</th>
    </tr>
    <#list userList as person>
     <tr>
       <td>${person.name}</td>
       <td>${person.age}</td>
       <td>${(person.age>25) ?string("old","young")}</td>
       <td>${(person.birthday?string('yyyy-MM-dd hh:mm:ss'))!} </td>
       <td>${person.active?string("是","否")}</td>
     </tr>
    </#list>
  </body>
</table>

    1. 编写相应的Controller

FreemarkerController:

@Controller
@RequestMapping("/freemarker")
public class FreemarkerController {

    @RequestMapping("/hello")
    public String hello(ModelMap map) {
        User u = new User();
        u.setName("haozz");
        u.setAge(24);
        u.setPassword("qwerty");
        u.setBirthday(new Date());
        u.setBirthday(null);
        u.setActive(false);
        
        User u1 = new User();
        u1.setName("nico robin");
        u1.setAge(35);
        u1.setPassword("qwerty");
        u1.setBirthday(new Date());
        u1.setActive(true);
        
        List<User> userList = new ArrayList<>();
        userList.add(u);
        userList.add(u1);
        map.addAttribute("userList",userList);
    return "freemarker";  
  }   

集成原理

SpringBoot的自动配置中含有FreeMarkerAutoConfiguration配置对象,该配置对象又导入了 FreeMarkerReactiveWebConfiguration配置对象,在里面创建了FreeMarkerConfigurer和FreeMarkerViewResolve 两个对象交给Spring管理,并且设置了默认的属性值,这些属性值来源于FreeMarkerProperties对象

常见的属性配置
spring.freemarker.enabled=true: 是否开启freemarker支持

spring.freemarker.allow-request-override: 是否允许request中的属性覆盖model中同名属性,默认false 

spring.freemarker.allow-session-override: 是否允许session中的属性覆盖model中同名属性,默认false 

spring.freemarker.cache: 是否支持模板缓存,默认false

spring.freemarker.charset=UTF-8: 模板编码

spring.freemarker.content-type=text/html: 模板contenttype 

spring.freemarker.expose-request-attributes: 是否开启request属性暴露,默认false

spring.freemarker.expose-session-attributes: 是否开启session属性暴露,默认false 

spring.freemarker.expose-spring-macro-helpers: 是否开启spring的freemarker宏支持,默认为false 

spring.freemarker.prefer-file-system-access: 默认为true,支持实时检查模板修改 

spring.freemarker.prefix: 加载模板时候的前缀

spring.freemarker.settings.*: 直接配置freemarker参数

spring.freemarker.suffix: 模板文件后缀 

spring.freemarker.template-loader-path=classpath:/templates/: 模板加载地址

集成Thymeleaf

为何要集成Thymeleaf

Thymeleaf是个XML/XHTML/HTML5模板引擎,可以用于Web与非Web应用。

Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。Thymeleaf的可扩展性也非常棒。你可以使用它定义自己的模板属性集合,这样就可以计算自定义表达式并使用自定义逻辑。这意味着Thymeleaf还可以作为模板引擎框架。

thymeleaf优点:静态html嵌入标签属性,浏览器可以直接打开模板文件,便于前后端联调。

Thymeleaf模板引擎是springboot中默认配置,与freemarker相似,可以完全取代jsp,在springboot中,它的默认路径是src/main/resources/templates 静态文件css, js 等文件默认路径是src/main/resources/static,所有项目中如果没有这个目录需要手动加上了

Thymeleaf的一些常见语法回顾

获取Controller控制器中共享给页面的对象中的普通属性

<input th:value="${user.age}"/>

获取Controller控制器中共享给页面的对象中的日期类型属性

<input th:value="${#dates.format(user.birthday,'yyyy-MM-dd')}"/>

获取Controller控制器中共享给页面的对象中的boolean类型属性

<td th:text="${person.flag}"></td>

获取Controller控制器中共享给页面的list集合,必须要遍历进行显示

<tr th:each="person:${userList}">
    <td th:text="${person.name}"></td>
    <td th:text="${person.age ge 25} ? old : young"></td>
    <td th:text="${#dates.format(person.birthday,'yyyy-MM-dd hh:mm:ss')}"></td>
    <td th:text="${person.flag}"></td>
</tr>

如果需要判断,使用if标签判断

<div th:if="${user.age lt 25}">young</div>

如果需要判断,可以使用switch标签判断

<div th:switch="${user.name}">
    <p th:case="'haozz'">最帅的</p>
    <p th:case="'nico robin'">妮可罗宾</p>
    <p th:case="'nami'">娜美</p>
</div>

处理表单(action跳转地址,method设置请求方式,object抽取相同的部分)

<form th:action="@{/th/postform}" th:object="${user}" th:method="post">  
<!--th:field="*{name}"相当于id="name" name="name" value="具体的name值"-->  
<input type="text" th:field="*{name}"/>
<input type="submit"/>
</form>

处理html内容被解析,转译

<span th:utext="${user.desc}"></span>
th:utext 会进行转译<br/>
th:text 不会进行转译<br/>
常见的th标签汇总
th标签汇总图.png
如何集成Thymeleaf
    1. 导入相关的依赖。
<!--整合thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
    1. 在resources/templates目录下面创建对应的thymeleaf模板。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:method="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table>
    <tr>
        <th>姓名</th>
        <th>年龄</th>
        <th>备注</th>
        <th>生日</th>
        <th>婚否</th>
    </tr>
    <tr th:each="person:${userList}">
        <td th:text="${person.name}"></td>
        <td th:text="${person.age}"></td>
        <td th:text="${person.age ge 25} ? old : young"></td>
        <td th:text="${#dates.format(person.birthday,'yyyy-MM-dd hh:mm:ss')}"></td>
        <td th:text="${person.active}"></td>
    </tr>
</table>
</body>
</html>

    1. 编写相应的Controller
@Controller
@RequestMapping("/freemarker")
public class FreemarkerController {

    @RequestMapping("/hello")
    public String hello(ModelMap map) {
        User u = new User();
        u.setName("haozz");
        u.setAge(24);
        u.setPassword("qwerty");
        u.setBirthday(new Date());
        u.setBirthday(null);
        u.setActive(false);
        
        User u1 = new User();
        u1.setName("nico robin");
        u1.setAge(35);
        u1.setPassword("qwerty");
        u1.setBirthday(new Date());
        u1.setActive(true);
        
        List<User> userList = new ArrayList<>();
        userList.add(u);
        userList.add(u1);
        map.addAttribute("userList",userList);
        return "freemarker"; 
  }

集成原理

SpringBoot的自动配置中含有ThymeleafAutoConfiguration配置对象,该配置对象的里面创建了视图解析器ThymeleafViewResolver和模板引擎 SpringTemplateEngine 和模板解析器SpringResourceTemplateResolver 三个对象交给Spring管理,并且设置了默认的属性值,这些属性值来源于ThymeleafProperties对象

常见属性设置
spring.thymeleaf.prefix=classpath:/templates/  设置thymeleaf路径默认为src/main/resources/templates
spring.thymeleaf.suffix=.html  设置thymeleaf模板后缀
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false  是否开启缓存默认为true
spring.thymeleaf.mode=LEGACYHTML5  设置thymeleaf严格校验
spring.thymeleaf.encoding=UTF-8    设置编码

了解@ConfigurtionProperties注解的用法:

属性名匹配规则

@ConfigurationProperties的属性名匹配规则(Relaxed binding):Spring Boot可以对下面四种类型的命名进行自动匹配,也就是说配置文件和类文件中的名称不需要完全相同,使用下面的任意两种,系统就会将二者匹配上

标准方式:变量之间通过下一个单词的首字母大写的方式隔开,
eg:student.isFemale
使用“-”进行单词之间的连接:
eg:student.is-female
使用“_”进行单词之间的连接:
eg:student.is_female
全部大写,使用“_”连接:通常对系统属性变量推荐这种写法。
eg:STUDENT_IS_FEMALE

属性ignoreInvalidFields

标记当有非法字段绑定到这个对象时可以被忽略。"非法"指根据使用的binder非法,通常这意味着字段类型错误(或者无法强制转为正确的类型).默认false。这意味着不能忽略字段非法,所以只能报错

属性ignoreUnknownFields

标记当有未知字段绑定到这个对象时可以被忽略。未知字段可能是属性中的一个符号错误。默认true。

属性prefix/value

要绑定到这个对象的有效的属性的名字前缀。value 和 prefix 是同义词

案例如下:


@ConfigurationProperties(prefix = "spring.datasource")
@Setter@Getter@ToString
@Repository
public class MyDataSource {
    private String username;
    private String url;
    private String password;
    private String driverClassName;
}

application.properties:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///crm
spring.datasource.username=root
spring.datasource.password=root123
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource


@Controller
@SpringBootApplication
public class DataSourcesController {

    @Autowired
    private MyDataSource myDataSource;

    @RequestMapping("/source")
    @ResponseBody
    public String hello() {

        return myDataSource.toString();
    }
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    } 
}

集成Druid连接池

为何要集成Druid

1、性能方面 hikariCP>druid>tomcat-jdbc>dbcp>c3p0 。hikariCP的高性能得益于最大限度的避免锁竞争。

2、druid功能最为全面,sql拦截等功能,统计数据较为全面,具有良好的扩展性。

3、综合性能,扩展性等方面,可考虑使用druid或者hikariCP连接池,比较方便对jdbc接口进行监控跟踪等。

4、可开启prepareStatement缓存,对性能会有大概20%的提升。

psCache是connection私有的,所以不存在线程竞争的问题,开启pscache不会存在竞争的性能损耗。
psCache的key为prepare执行的sql和catalog等,value对应的为prepareStatement对象。开启缓存主要是减少了解析sql的开销。

5、3p0历史悠久,代码及其复杂,不利于维护。并且存在deadlock的潜在风险。

6、Druid可以打印SQL,慢查询方面的日志

回顾Druid连接池在SSM框架中的用法

在pom.xml文件中导入依赖:

<!-- MySQL驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.38</version>
      <scope>runtime</scope>
    </dependency>

    <!-- druid连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.0.26</version>
    </dependency>
    <!--spring jdbc-->
 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    

spring的主配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--组件扫描器-->
    <context:component-scan base-package="cn.wolfcode.crm"/>
    <!--自动DI解析器-->
    <context:annotation-config/>
    <!--加载数据库的配置信息-->
    <context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/>

    <!-- 连接池对象 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

如何集成Druid

方式一

pom.xml:

 <!--整合DrudiDataSource-->
   <!--mysql-->
   <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
   </dependency>
   <!--druid-->
   <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
   </dependency>
   <!--jdbc-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-jdbc</artifactId>
 </dependency>       
        

application.properties:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///crm
spring.datasource.username=root
spring.datasource.password=root123
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

方式二

pom.xml:

<!--整合DrudiDataSource-->
   <!--mysql-->
   <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
   </dependency>
   <!--druid-->
   <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.10</version>
   </dependency>
   <!--jdbc-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-jdbc</artifactId>
 </dependency> 
 

application.properties:

spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql:///crm
spring.datasource.druid.username=root
spring.datasource.druid.password=root123

测试案例:

@RestController
public class DataSourceController {

    @Autowired
    private DataSource dataSource;

    @RequestMapping("/dataSource")
    public String showData() throws SQLException {
        return dataSource.getConnection().toString();
    }
}

集成原理

SpringBoot的自动配置中含有DataSourceAutoConfiguration配置对象,该配置对象含有个内部类,该内部类也是配置 类,当容器中没有连接池对象时,就会Generic方式来创建连接池对象交给Spring管理,用到是属性值来源于 DataSourceProperties对象

集成Junit框架

为何要集成Junit

我们在开发的过程中,肯定需要不断的对我们自己写的代码进行测试。所以离不开Junit单元测试。

  1. 导入依赖:
<!--junit-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

  1. 准备配置类
public class SomBean {

    public SomBean() {
        System.out.println("构造器");
    }
}

@Configuration
public class JunitConfig {

    @Bean
    public SomBean somBean(){
        return  new SomBean();
    }
}

  1. 编写测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = JunitConfig.class)
public class App 
{

    @Autowired
    private SomBean somBean;

    @Test
    public void test(){
        System.out.println("somBean = " + somBean);
    }

}

集成MyBatis框架

为何要集成MyBatis

1、简单易学

   mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

2、灵活

   mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。

3、解除sql与程序代码的耦合

   通过提供DAL层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

4、提供映射标签,支持对象与数据库的orm字段关系映射

5、提供对象关系映射标签,支持对象关系组建维护

6、提供xml标签,支持编写动态sql(主要优势之一)。

回顾MyBatis在之前SSM框架中集成的方式

pom.xml:

<!-- mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>${mybatis.version}</version>
    </dependency>

    <!-- mybatis-spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>
    

applicationContext.xml:

 <!-- 配置SessionFactory -->
    <bean id="sessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 1:连接池 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 2:读取MyBatis总配置文件 -->
        <property name="configLocation" value="classpath:mybatis.xml"/>
        <!-- 3:配置别名扫描 -->
        <property name="typeAliasesPackage" value="cn.wolfcode.crm.domain"/>
        <!-- 4:加载mapper文件 -->
        <property name="mapperLocations" value="classpath:cn/wolfcode/crm/mapper/*Mapper.xml"/>
        <!--集成pagehelper-->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <!-- 这里的几个配置主要演示如何使用,如果不理解,一定要去掉下面的配置 -->
                    <property name="properties">
                        <value>
                            helperDialect=mysql
                            reasonable=true
                            pageSizeZero=true
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>
    
<!--Mapper接口代理扫描器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.wolfcode.crm.mapper"/>
    </bean>
    

mybatisCfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <!--配置允许懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--取消关联查询积极性-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--哪些方法触发关系查询-->
        <setting name="lazyLoadTriggerMethods" value="clone"/>
    </settings> 
</configuration>

如何集成MyBatis

pom.xml:

 <!--整合mybatis-->
 <dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>1.3.0</version>
 </dependency>

mybatis-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <!--配置允许懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--取消关联查询积极性-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--哪些方法触发关系查询-->
        <setting name="lazyLoadTriggerMethods" value="clone"/>
    </settings> 
</configuration>

application.properties:

#mybatis
#给包起别名,因为使用MBG生成的mapper,
#sql里面默认是使用的类的全限定名,因此我们可以不配别名,都使用全名
#mybatis.type-aliases-package=cn.wolfcode.springboot.integrase._5_mybatis.domain
#加载mybatis的主配置文件
mybatis.config-location=classpath:mybatis-config.xml

我们也可以去掉MyBatis的主配置文件:把懒加载的配置配置到SpringBoot的配置文件中:

如:

mybatis.configuration.lazy-load-trigger-methods=clone
mybatis.configuration.aggressive-lazy-loading=false
mybatis.configuration.lazy-loading-enabled=true

domain:

@Setter
@Getter
@ToString
public class Department {

    private Long id;
    private String name;
    private String sn;

}

mapper:

public interface DepartmentMapper {


    List<Department> selectAll();

}

mapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.wolfcode.springboot._06_mybatis.mapper.DepartmentMapper">
    <select id="selectAll"
            resultType="cn.wolfcode.springboot._06_mybatis.domain.Department">
    select id,name,sn from department
  </select>
</mapper>

主配置类:

注意:通过@MapperScan扫描器来扫描mapper的接口


@SpringBootApplication
@MapperScan("cn.wolfcode.springboot._06_mybatis.mapper")
public class MyBatisConfig {
    
}

App测试类:


@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyBatisConfig.class)
public class App 
{
    @Autowired
    private DepartmentMapper departmentMapper;

    @Test
    public void test(){
        List<Department> list = departmentMapper.selectAll();
        list.forEach(System.out::println);
    }
}

常见属性
#懒加载
mybatis.configuration.lazy-loading-enabled=true mybatis.configuration.lazy-load-trigger-methods=clone 


mybatis.mapper-locations=classpath:cn/wolfcode/xxx/mapper/*Mapper.xml 

mybatis.type-aliases-package=cn.wolfcode.xxx.domain #连接池对象不用配置,会自动注入

集成PageHelper

回顾PageHelper在之前SSM框架中集成的方式

pom.xml:

 <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.6</version>
 </dependency>
 

配置文件中配置pageHelper

方式一:分页插件配置在mybatis的主配置文件中

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
       PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
   <settings>
       <!--配置允许懒加载-->
       <setting name="lazyLoadingEnabled" value="true"/>
       <!--取消关联查询积极性-->
       <setting name="aggressiveLazyLoading" value="false"/>
       <!--哪些方法触发关系查询-->
       <setting name="lazyLoadTriggerMethods" value="clone"/>
   </settings>


   <!--配置在Spring中-->
    <plugins>
       <plugin interceptor="com.github.pagehelper.PageInterceptor">
           <property name="reasonable" value="true"/>
           <property name="helperDialect" value="mysql"/>
           <property name="pageSizeZero" value="true"/>
       </plugin>
   </plugins>

</configuration>

方式二:分页插件配置在spring的配置文件中

 <!-- 配置SessionFactory -->
   <bean id="sessionFactory"
         class="org.mybatis.spring.SqlSessionFactoryBean">
       <!-- 1:连接池 -->
       <property name="dataSource" ref="dataSource"/>
       <!-- 2:读取MyBatis总配置文件 -->
       <property name="configLocation" value="classpath:mybatis.xml"/>
       <!-- 3:配置别名扫描 -->
       <property name="typeAliasesPackage" value="cn.wolfcode.crm.domain"/>
       <!-- 4:加载mapper文件 -->
       <property name="mapperLocations" value="classpath:cn/wolfcode/crm/mapper/*Mapper.xml"/>
       <!--集成pagehelper-->
       <property name="plugins">
           <array>
               <bean class="com.github.pagehelper.PageInterceptor">
                   <!-- 这里的几个配置主要演示如何使用,如果不理解,一定要去掉下面的配置 -->
                   <property name="properties">
                       <value>
                           helperDialect=mysql
                           reasonable=true
                           pageSizeZero=true
                       </value>
                   </property>
               </bean>
           </array>
       </property>
   </bean>
    
如何集成PageHelper

pom.xml:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.10</version>
</dependency>

application.properties:


#pageHelper
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.page-size-zero=true

集成事务管理

回顾事务管理在之前SSM框架中的使用

方式一:在spring的xml配置文件中

    <!-- 配置事务 -->
    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="list*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut expression="execution(* cn.wolfcode.crm.service.*Service.*(..))" id="txPoint"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
    </aop:config>

方式二:使用注解的方式

        <!-- 配置事务管理器 -->
    <bean id="transactionManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 启用事务注解 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    

Spring中注解的方式@Transactional标注事务方法。为了将方法定义为支持事务处理,可以在方法上添加@Transactional注解。根据Spring AOP基于代理机制,只能标注公有方法。如果在类上标注@Transactional注解,那么这个类中所有公有方法都会被定义为支持事务。

代码如下:

        //添加事务注解
    //1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
    //如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
    //REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起. 
    //2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED
    //3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的
    //属性进行设置. 通常情况下去默认值即可. 
    //4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据, 
    //这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
    //5.使用 timeout 指定强制回滚之前事务可以占用的时间.
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={UserAccountException.class},
            rollbackFor = IOException.class,
            readOnly=false,
            timeout=3)
    @Override
    public void purchase(String username, String isbn) {}
        

注意有一种情况事务会失效:

    @Transactional
    @Override
    public void purchase(String username, String isbn) {
        this.update(username, isbn);
    }
 
    @Transactional
    public void update(String username, String isbn) {
         // TO DO
    }

失效的原因是:

因为spring事务是基于aop的代理机制,当方法中调用this本身的方法时候即使在this的方法标明事务注解,但是事务注解会失效。

解决办法:

在配置中添加如下内容

<!--开启aspectj代理,并暴露aop代理到ThreadLocal-->
<aop:aspectj-autoproxy expose-proxy="true"/>

将上述调用的地方改成如下:

@Transactional
@Override
public void purchase(String username, String isbn) {
    ((BookShopServiceImpl)AopContext.currentProxy()).update(username, isbn);
}

如何集成事务管理

方式一: 使用注解的方式

在Service的实现类上面添加@Transactional注解即可。

如果是使用的springboot的较低版本,需要在配置类上面使用@EnableTransactionManagement开启注解,可以理解为相当于

<tx:annotation-driven transaction-manager="transactionManager"/>

方式二:使用xml文件的方式

  1. 提供一个配置事务的xml文件。
<!-- 3:配置事务管理 -->
    <!-- 3w: what where when -->
    <!-- 3.1:what 做什么增强 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 3.2:where: 在哪个包哪个类哪个方法下做增强 -->
    <aop:config>  
        <aop:pointcut expression="execution( * cn.wolfcode.crm.service.*Service.*(..))" id="pc"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
    </aop:config>
    
    <!-- 3.3:when :在什么时候做增强 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="list*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="check*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    

2.在主配置文件中通过@ImportResources

@ImportResource("classpath:tx.xml")

集成FastJSON

为何要集成FastJSON

fastjson可以只依赖于jdk环境,其他的条件可以不用依赖

fastjson可以进行序列化和反序列换(所谓的序列化就是将对象转换成对应的json字符串,反序列化就是将json字符串进行转换成对应的对象)

fastjson进行json转换的的速度远远大于jackjson

回顾FastJSON在之前SSM框架中的使用

pom.xml:

<!--fastJson解析json  -->
 <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.29</version>
</dependency>

mvc.xml

<mvc:annotation-driven  >
<mvc:message-converters register-defaults="true">
            <!-- 配置Fastjson支持 -->
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json</value>
                        <value>application/xml;charset=UTF-8</value>  
                    </list>
                </property>
                <property name="features">
                    <list>
                    <!--配置返回json字符串是返回null值  -->
                        <value>WriteMapNullValue</value>
                        <value>QuoteFieldNames</value>
                        <!-- 配置日期格式化 -->
                          <value>WriteDateUseDateFormat</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
</mvc:annotation-driven>

@jsonFiled(format="yyyy-MM-dd HH:mm:ss")
private Date creadate;

如何集成FastJSON

pom.xml:

  <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.29</version>
</dependency>

方式一:

在一个配置类中定义一个bean

 @Bean
public HttpMessageConverters fastjsonHttpMessageConverters(){
//定义一个convert转换消息的对象
FastJsonHttpMessageConverter converter=new FastJsonHttpMessageConverter();

//添加fastjson的配置信息,比如是否要格式化返回的json信息
FastJsonConfig fastJsonConfig=new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
//解决fastjson 中文乱码
List<MediaType> fastMediaTypes = new ArrayList<MediaType>();
    fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
       
//将配置信息添加到convert中
converter.setFastJsonConfig(fastJsonConfig);
//将编码配置加入convert中
converter.setSupportedMediaTypes(fastMediaTypes);
HttpMessageConverter<?> httpMessageConverter=converter;
return new HttpMessageConverters(converter);
}

方式二:

让配置类实现 接口 WebMvcConfigurer ,然后覆盖一个方法configureMessageConverters 如:


 @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
       // 1、需要先定义一个 convert 转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

        //2、添加fastJson 的配置信息,比如:是否要格式化返回的json数据;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(
                SerializerFeature.PrettyFormat
        );

        //3、在convert中添加配置信息.
        fastConverter.setFastJsonConfig(fastJsonConfig);

        //4、将convert添加到converters当中.
        converters.add(fastConverter);
    }
    

注解:

// 输出到页面,格式化日期格式
@JSONField(format = "yyyy-MM-dd")
// serialize:是否需要序列化属性.如果设置成false,页面上就不会出现

@JSONField(serialize = false)

集成Jackson

SPringBoot已经集成了Jackson,所以我们不需要导入依赖。可直接使用。
 private Long id;
    private String name;
    // 输出日期到表单,进行格式化 。注意:必须要带上时区,不然时间会减少一天
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
    // 接收表单传来到日期,进行格式化。
    @DateTimeFormat(pattern ="yyyy-MM-dd")
    private Date hiredate;

    @JsonIgnore // 忽略该属性在浏览器中进行显示数据
    private String password;

集成SpringData JPA

JPA是什么

SpringData:其实SpringData就是Spring提供了一个操作数据的框架。而SpringData JPA只是SpringData框架下的一个基于JPA标准操作数据的模块。

SpringData JPA:基于JPA的标准数据进行操作。简化操作持久层的代码。只需要编写接口就可以。

如何集成JPA
  1. 导入依赖
<!--jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

  1. application.properties文件中添加配置
#jpa
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

  1. 实体类
@Entity
@Table(name="role")
public class Role {

    @Id    //主键id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//主键生成策略
    @Column(name = "id")//数据库字段名
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "sn")
    private String sn;

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sn='" + sn + '\'' +
                '}';
    }
}

  1. 编写Dao接口
@Repository
public interface RoleRepository extends JpaRepository<Role,Long> {


    Role findRoleById(Long id);

    Role findRoleByName(String name);


}
  1. 测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class JPATest {

    @Autowired
    private RoleRepository roleRepository;

    @Test
    public void testFindById(){
        Role  role = roleRepository.findRoleById(2L);
        System.out.println(role);
    }

    @Test
    public void testFindByName(){
        Role  role = roleRepository.findRoleByName("人事管理");
        System.out.println(role);
    }
}

SpringBoot统一异常处理

SpringBoot为何需要统一异常处理

在 Java EE 项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过 程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。 每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。 那么将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一, 也实现了异常信息的统一处理和维护

统一异常处理在之前SSM框架中的处理方式回顾

Spring MVC 处理异常有 3 种方式:

(1)使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver; 
(2)实现 Spring 的异常处理接口 HandlerExceptionResolver 自定义自己的异常处理器; 
(3)使用@ExceptionHandler 注解实现异常处理;

回顾方式一:

SecurityException自定义异常类:

public class SecurityException extends Exception {
    
    public SecurityException() {    
        super();    
    }   
    public SecurityException(String message){
        super(message);
    }
 }

springmvc.xml中的配置全局异常处理器:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> 
<!-- 定义默认的异常处理页面,当该异常类型的注册时使用 -->
<property name="defaultErrorView" value="commons/error" />
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为 exception -->
<property name="exceptionAttribute" value="ex" />
<!-- 定义需要特殊处理的异常,用类名或完全路径名作为 key,异常也页名作为值 --> 
    <property name="exceptionMappings">
        <value> cn.wolfcode.web.exception.SecurityException=commons/nopermission 
<!-- 这里还可以继续扩展不同异常类型的异常处理 -->
        </value>
    </property>
</bean>

回顾方式二:

CustomException自定义异常类:

/**
 *
 * 系统自定义异常类
 */
public class CustomException extends Exception {
    //异常信息
    public String message;
    public CustomException(String message){
        super(message);
        this.message = message;
    }
    @Override
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}

全局异常处理器:

/**
 * 
 * 全局异常处理器
 */
public class CustomExceptionResolver implements HandlerExceptionResolver{
    /**
     * 系统抛出的异常
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param e 系统抛出的异常
     * @return
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
       // 解析出异常类型
        CustomException customException = null;
        // 若该异常类型是系统自定义的异常,直接取出异常信息在错误页面展示即可。
        if(e instanceof CustomException){
            customException = (CustomException)e;
        }else{
            // 如果不是系统自定义异常,构造一个系统自定义的异常类型,信息为“未知错误”
            customException = new CustomException("未知错误");
        }
        //错误信息
        String message = customException.getMessage();
        ModelAndView modelAndView = new ModelAndView();
        //将错误信息传到页面
        modelAndView.addObject("message",message);
        //指向错误页面
        modelAndView.setViewName("error");
        return modelAndView;
    }
}

错误页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>失败!</title>
</head>
<body>
${message}
</body>
</html>

springmvc.xml中的配置全局异常处理器:

    <!--
    全局异常处理器
    只要类实现了HandlerExceptionResolver接口,就是一个全局异常处理器哦
    -->
    <bean class="com.alex.ssm.exception.CustomExceptionResolver"/>

回顾方式三:

@ControllerAdvice
通过使用@ControllerAdvice定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler用来定义函数针对的异常类型。
�ErrorControllerAdvice:

@ControllerAdvice
public class ErrorControllerAdvice {

    @ExceptionHandler(Exception.class)
    public String error(Model model, Exception e){
         model.addAttribute("error",e.getMessage());
         return "error";
    }
}

error.ftl:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   ${error}
</body>
</html>
在SpringBoot中如何统一异常处理

方式一:

通过使用@ControllerAdvice定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler用来定义函数针对的异常类型。

ErrControllerAdvice:

@ControllerAdvice
public class ErrControllerAdvice {

    @ExceptionHandler
    public String error(Exception e , Model model){
        model.addAttribute("error",e.getMessage());
        return "error";
    }
}

注意:如果项目中报错,会被上述异常进行拦截。上述方法,它会拦截到异常,并把异常信息存入model中,然后跳转error的页面,这个页面需要自己编写,在templates模版文件夹中。

error.ftl:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
  我是通过注解ControllerAdvice进来的错误页面
  error : ${error}
</body>
</html>

利用统一异常的处理类,我们可以自定义自己的异常,比如,自定义异常,返回json格式的数据。

第一步:自定义异常类

public class MyCustomException extends  RuntimeException {

    public MyCustomException(String message) {
        super(message);
    }

    public MyCustomException(Throwable cause) {
        super(cause);
    }
}

第二步:在异常的处理类中添加自定义异常的拦截方法

@ExceptionHandler
    @ResponseBody
    public JSONResult customException(MyCustomException e) {
        JSONResult jsonResult = new JSONResult();
        jsonResult.setMark(e.getMessage());
        return jsonResult;
    }


第三步:在service层添加异常的处理(try...catch(自定义异常))

@Service
public class ExceptionServiceImpl {

    public void exception() {
        try {
            int i = 1 / 0;
        }catch (Exception e){
            throw new MyCustomException(e.getMessage());
        }
    }
}

第四步: 编写对应的controller,在方法中,调用service中的方法。

 @RequestMapping("/ex")
    @ResponseBody
    public JSONResult list2(){

        ExceptionServiceImpl service = new ExceptionServiceImpl();
        service.exception();

        return new JSONResult();

    }

方式二:

1,SpringBoot默认情况下,把所有错误都重新定位到/error这个处理路径上,由BasicErrorController类完成处理;

2,SpringBoot提供了默认的替换错误页面的路径:

1,静态错误页面默认结构:
src/
    resources/
    public/
        error/
            404.html
            401.html
            5xx.html�
2,也可以使用模板页面:
src/
    resources/
    templates/
        error/
            5xx.ftl

该路径方式是通过ErrorMvcAutoConfiguration中的DefaultErrorViewResolver完成的;

最后编辑于:2024-11-17 10:24:55


喜欢的朋友记得点赞、收藏、关注哦!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2253391.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3项目最新eslint9+prettier+husky+stylelint+vscode配置

一、eslint9和prettier通用配置 安装必装插件 ESlint9.x pnpm add eslintlatest -DESlint配置 vue 规则 , typescript解析器 pnpm add eslint-plugin-vue typescript-eslint -DESlint配置 JavaScript 规则 pnpm add eslint/js -D配置所有全局变量 globals pnpm add globa…

LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型 Matlab代码注释清晰。 程序设计 完整程序和数据获取方式&#xff1a;私信博主回复LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型&#xff08;Matlab&#…

【数据集】细胞数据集:肿瘤-胎儿重编程的内皮细胞驱动肝细胞癌中的免疫抑制性巨噬细胞(Sharma等人)

引用此数据集&#xff1a; Sharma, Ankur (2020), “Onco-fetal reprogramming of endothelial cells drives immunosuppressive macrophages in Hepatocellular Carcinoma (Sharma et al)”, Mendeley Data, V1, doi: 10.17632/6wmzcskt6k.1 下载地址&#xff1a;Onco-feta…

A30 PHP+MYSQL+LW+工厂库存仓储订单销售后台管理系统的设计与实现 源代码 配置 文档

工厂库存仓储订单销售管理系统 1.摘要2.开发目的和意义3.系统功能设计4.数据库设计5.系统界面截图6.源码获取 1.摘要 互联网给人们生活带来了极大的转变&#xff0c;现代化、信息化管理系统也成为企业管理的必要手段。电子信息化服务模式&#xff0c;一切以信息化手段来处理和…

利用Docker一键发布Nginx-Tomcat-MySQL应用集群

Docker简介&#xff0c;可以看上一篇文章&#xff1a; 为什么互联网公司离不开Docker容器化&#xff0c;它到底解决了什么问题&#xff1f;-CSDN博客 Docker体系结构 docker核心就是镜像和容器&#xff1a; 镜像就是应用程序的安装文件&#xff0c;包含了所有需要的资源&…

python使用python-docx处理word

文章目录 一、python-docx简介二、基本使用1、新建与保存word2、写入Word&#xff08;1&#xff09;打开文档&#xff08;2&#xff09;添加标题&#xff08;3&#xff09;添加段落&#xff08;4&#xff09;添加文字块&#xff08;5&#xff09;添加图片&#xff08;6&#xf…

echarts的双X轴,父级居中的相关配置

前言&#xff1a;折腾了一个星期&#xff0c;在最后一天中午&#xff0c;都快要放弃了&#xff0c;后来坚持下来&#xff0c;才有下面结果。 这个效果就相当是复合表头&#xff0c;第一行是子级&#xff0c;第二行是父级。 子级是奇数个时&#xff0c;父级label居中很简单&…

CTF-PWN: WEB_and_PWN [第一届“吾杯”网络安全技能大赛 Calculator] 赛后学习(不会)

附件 calculate.html <!DOCTYPE html> <html lang"en"> <head><!-- 设置字符编码为 UTF-8&#xff0c;支持多语言字符集 --><meta charset"UTF-8"><!-- 设置响应式视图&#xff0c;确保页面在不同设备上自适应显示 --&…

STM32 PWM波形详细图解

目录 前言 一 PWM介绍 1.1 PWM简介 1.2 STM32F103 PWM介绍 1.3 时钟周期与占空比 二.引脚映像关系 2.1引脚映像与寄存器 2.2 复用功能映像 三. PWM 配置步骤 3.1相关原理图 3.2配置流程 3.2.1 步骤一二&#xff1a; 3.2.2 步骤三&#xff1a; 3.2.3 步骤四五六七&#xff1a; …

6.824/6.5840 Lab 1: MapReduce

宁静的夏天 天空中繁星点点 心里头有些思念 思念着你的脸 ——宁夏 完整代码见&#xff1a; https://github.com/SnowLegend-star/6.824 由于这个lab整体难度实在不小&#xff0c;故考虑再三还是决定留下代码仅供参考 6.824的强度早有耳闻&#xff0c;我终于也是到了挑战这座高…

东方隐侠网安瞭望台第8期

谷歌应用商店贷款应用中的 SpyLoan 恶意软件影响 800 万安卓用户 迈克菲实验室的新研究发现&#xff0c;谷歌应用商店中有十多个恶意安卓应用被下载量总计超过 800 万次&#xff0c;这些应用包含名为 SpyLoan 的恶意软件。安全研究员费尔南多・鲁伊斯上周发布的分析报告称&…

【python自动化一】pytest的基础使用

1.pytest简述 pytest‌ 是一个功能强大且灵活的Python测试框架&#xff0c;其主要是用于流程控制&#xff0c;具体用于UI还是接口自动化根据个人需要而定。且其具有丰富插件&#xff0c;使用时较为方便。咱们具体看下方的内容&#xff0c;本文按照使用场景展开&#xff0c;不完…

EasyDSS视频推拉流技术的应用与安防摄像机视频采集参数

安防摄像机的视频采集参数对于确保监控系统的有效性和图像质量至关重要。这些参数不仅影响视频的清晰度和流畅度&#xff0c;还直接影响存储和网络传输的需求。 安防摄像机图像效果的好坏&#xff0c;由DSP处理器和图像传感器sensor决定&#xff0c;如何利用好已有的硬件资源&…

GoReplay开源工具使用教程

目录 一、GoReplay环境搭建 1、Mac、Linux安装GoReplay环境 二、GoReplay录制与重播 1、搭建练习接口 2、录制命令 3、重播命令 三、GoReplay单个命令 1、常用命令 2、其他命令 3、命令示例 4、性能测试 5、正则表达式 四、gorepaly组合命令 1、组合命令实例 2、…

论文:IoU Loss for 2D/3D Object Detection

摘要&#xff1a;在2D/3D目标检测任务中&#xff0c;IoU (Intersection-over- Union)作为一种评价指标&#xff0c;被广泛用于评价不同探测器在测试阶段的性能。然而&#xff0c;在训练阶段&#xff0c;通常采用常见的距离损失(如L1或L2)作为损失函数&#xff0c;以最小化预测值…

CAD 文件 批量转为PDF或批量打印

CAD 文件 批量转为PDF或批量打印&#xff0c;还是比较稳定的 1.需要本地安装CAD软件 2.通过 Everything 搜索工具搜索&#xff0c;DWG To PDF.pc3 &#xff0c;获取到文件目录 &#xff0c;替换到代码中&#xff0c; originalValue ACADPref.PrinterConfigPath \ r"C:…

【错误记录】jupyter notebook打开后服务器错误Forbidden问题

如题&#xff0c;在Anaconda Prompt里输入jupyter notebook后可以打开浏览器&#xff0c;但打开具体项目后就会显示“服务器错误&#xff1a;Forbidden”&#xff0c;终端出现&#xff1a; tornado.web.HTTPError: HTTP 403: Forbidden 查看jupyter-server和jupyter notebook版…

[MacOS] [kubernetes] MacOS玩转虚拟化最佳实践

❓ 为什么不在MacOS本机安装呢&#xff1f;因为M系列芯片是Arm架构&#xff0c;与生产环境或者在本地调试时候&#xff0c;安装虚拟镜像和X86不同&#xff0c;造成不必要的切换环境的额外成本&#xff0c;所以在虚拟化的x86调试 步骤 & 详情 一: 安装OrbStack & 并配置…

网络编程相关 API 学习

目录 1. 网络编程中的基本概念 2. UDP 的 socket api 的使用 (1) DatagramSocket API (2) DatagramPacket API (3) InetSocketAddress API (4) 使用 UDP 的 socket api 3. TCP 的 socket api 的使用 (1) ServerSocket API (2) Socket API 1. 网络编程中的基本概念 客…

【Android】View工作原理

View 是Android在视觉上的呈现在界面上Android提供了一套GUI库&#xff0c;里面有很多控件&#xff0c;但是很多时候我们并不满足于系统提供的控件&#xff0c;因为这样就意味这应用界面的同类化比较严重。那么怎么才能做出与众不同的效果呢&#xff1f;答案是自定义View&#…