美文网首页
SpringBoot两天

SpringBoot两天

作者: 祥祺 | 来源:发表于2019-12-31 08:29 被阅读0次

    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完成的;

    相关文章

      网友评论

          本文标题:SpringBoot两天

          本文链接:https://www.haomeiwen.com/subject/baupvctx.html