Spring注解驱动

Lou.Chen
大约 28 分钟

一、组件注册

1、@Bean和@Configuration

  • @Bean 注册bean到容器中
    • 方法返回类型为相当于bean中的class属性
    • 方法名称默认为相当于bean中的id属性
    • 通过 @Bean(name/value = "person01") 方式指定bean的id属性
  • @Configuration 相当于xml文件
1)传统注册bean的方式:

实体

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private Integer age;
}

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="org.lc.entity.Person" id="person">
            <property name="age" value="12"></property>
            <property name="name" value="张三"></property>
        </bean>
</beans>
public class T1 {
    public static void main(String[] args) {
        //xml方式注册bean并获取bean
        ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean.xml");
        Person person = ((Person) classPathXmlApplicationContext.getBean("person"));
        System.out.println(person);
    }
}
2)基于注解的注册:
//使用@Configuration注册该类为配置文件(相当于xml配置文件)
@Configuration
public class BeanConfiguration {
    //使用@Bean注册到容器 (相当于bean注册到xml中)
    //默认方法返回的类型为 bean中的class
    //方法的名称为 bean中的id
    //name属性指定bean的id名称
    @Bean(name = "person01")
    public Person person() {
        //直接new对象方式为属性赋值
        return new Person("张三",20);
    }
}
public class T1 {
    public static void main(String[] args) {
        //注解方式注册并获取bean
        //获取该配置文件对象
        ApplicationContext annotation = new AnnotationConfigApplicationContext(BeanConfiguration.class);
        //获取指定的bean对象
        Person bean = annotation.getBean(Person.class);
        //获取该bean的id名称
        String[] beanDefinitionNames = annotation.getBeanNamesForType(bean.getClass());
        System.out.println(Arrays.toString(beanDefinitionNames));
    }
}

2、@Component和@Configuration注册组件区别

https://blog.csdn.net/isea533/article/details/78072133open in new window

测试Bean

public class Country {
}
@Getter
@Setter
public class UserInfo {
    private Country country;
    public UserInfo(Country country) {
        this.country = country;
    }
    public UserInfo() {
    }
}
1)使用@Component
@Component
public class MyBeanConfig {
    @Bean
    public Country country() {
        return new Country();
    }
    @Bean
    public UserInfo userInfo() {
        return new UserInfo(country());
    }
}

获取UserInfo中的Country对象

    public static void main(String[] args) {
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig.class);
        Country Country= annotation.getBean(Country.class);
        UserInfo userInfo= annotation.getBean(UserInfo.class);
        //false
        System.out.println(Country==userInfo.getCountry());
    }

我们发现通过在**@Component中注册的组件Country在同类配置直接使用的时候并不是单例**的,且country()并不会被Spring代理,会直接调用country()方法获取一个全新的Country对象实例,所以全局会有多个Country对象的实例。而new UserInfo(country())只是纯JAVA方式的调用,多次调用该方法返回的是不同的对象实例。

解决方式

通过注入的方式得到单例对象引用即可

@Component
public class MyBeanConfig {

    //通过注入即得到单例对象
    @Autowired
    private Country country;

    @Bean
    public Country country() {
        return new Country();
    }
    @Bean
    public UserInfo userInfo() {
        return new UserInfo(country);
    }
}
    public static void main(String[] args) {
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig.class);
        Country Country= annotation.getBean(Country.class);
        UserInfo userInfo= annotation.getBean(UserInfo.class);
        //true
        System.out.println(Country==userInfo.getCountry());
    }
2)使用@Configuration
@Configuration
public class MyBeanConfig {
    @Bean
    public Country country() {
        return new Country();
    }
    @Bean
    public UserInfo userInfo() {
        return new UserInfo(country());
    }
}
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig.class);
        Country Country= annotation.getBean(Country.class);
        UserInfo userInfo= annotation.getBean(UserInfo.class);
        //true
        System.out.println(Country==userInfo.getCountry());

我们发现通过在**@Configuration中注册的组件Country在同类配置直接使用的时候是单例的,因为new UserInfo(country())这段代码中country()方法会由Spring代理执行,Spring发现方法所请求的Bean已经在容器中,那么就直接返回容器中的Bean。所以全局只有一个Country**对象的实例。

​ 在@Configuration中注册的Bean标记的方法会被包装成CGLIB的wrapper,其工作原理是:如果方式是首次被调用那么原始的方法体会被执行并且结果对象会被注册到Spring上下文中。之后所有的对该方法的调用仅仅只是从Spring上下文中取回该对象返回给调用者。

3、@ComponentScan包扫描的使用

//修改默认规则为false(默认规则时扫描所有),只扫描注解为@controller
@ComponentScan(basePackages = "org.lc",useDefaultFilters = false,includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes ={Controller.class} )
})
//注解类型的,且为@Controller注解的不扫描
@ComponentScan(basePackages = "org.lc",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})

@ComponentScan为可重复注解即可以使用**@ComponentScans**包含多个扫描器

@ComponentScans({
        @ComponentScan(basePackages = "org.lc"),
        @ComponentScan(basePackages = "org.lc")
})

FilterType过滤类型介绍:

  • FilterType.ANNOTATION:按照注解过滤

  • FilterType.ASSIGNABLE_TYPE : 按照类型进行过滤

     @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {EmployeeService.class})
    
  • FilterType.REGEX 按照正则表达式过滤

  • FilterType.CUSTOM 自定义规则

    • @ComponentScan(basePackages = "org.lc",useDefaultFilters = false,includeFilters = {
              @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyFilter.class})
      })
      
    • //需要实现 TypeFilter接口
      public class MyFilter implements TypeFilter {
          @Override
          public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
              //获取当前类的注解的信息
              AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
              //获取当前正在扫描的类的class类信息
              ClassMetadata classMetadata = metadataReader.getClassMetadata();
              //获取当前类资源(路径)
              Resource resource = metadataReader.getResource();
              //获取当前类的类名
              String className = classMetadata.getClassName();
              System.out.println("------:"+className);
              //自定义类名中包含为'er'的字符串就加入到容器中
              if (className.contains("er")) {
                  return true;
              }
              //返回true 扫描到容器中
              //返回true 不扫描到容器中
              return false;
          }
      }
      
    •     @Test
          public void Test2(){
              AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringApplication.class);
              String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
              for (String s : beanDefinitionNames) {
                  System.out.println(s);
              }
          }
      }
      

4、@Scope使用

  • singletion 单实例的

    • bean默认注册的都为单实例的
    • IOC每次启动的时候都会初始化该bean放入IOC容器
  • prototype 多实例的

    • IOC容器启动并不会调用方法创建对象放在容器中,而是在用到该bean时该bean才会被初始化(懒加载)

    • @Configuration
      public class MyBeanConfig1 {
          @Scope("prototype")
          @Bean
          public Person person() {
              return new Person("张三",11);
          }
      }
      
    •     @Test
          public void Test3(){
              ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig1.class);
              Person bean = annotation.getBean(Person.class);
              Person bean1 = annotation.getBean(Person.class);
              //false
              System.out.println(bean==bean1);
          }
      
  • request 同一次请求为单实例的

  • session 同一个session创建一个实例

5、@Lazy懒加载

默认的bean对象都是在IOC启动的时候就加载到容器中,若要实现我们的单例bean的懒加载,即IOC容器启动的时候并不会创建该bean,而是在第一次获取该bean的时候才创建该bean对象

@Configuration
public class MyBeanConfig1 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("初始化bean");
        return new Person("张三",11);
    }
}

6、@Conditional按条件注册bean

@Conditional注解中传入一个Condition接口实例类,通过matches方法返回true或false。如果方法返回true,则该条件下的bean配置生效。如果返回false,则bean配置失效

  • @Conditional 在类上
    • 若实现Condition接口中的matches方法返回false,则该配置类中的所有bean都不会生效(都不会注册到IOC容器中)
  • @Conditional 在bean方法上
    • 若若实现Condition接口中的matches方法返回false,则该bean不会生效(不会注册到IOC容器中)

配置类

//若WindowsCondition中的matches为false则该配置下的所有bean不生效
@Conditional({WindowsCondition.class})
@Configuration
public class MyBeanConfig2 {

    //如果操作系统是linux 则给容器注册@Bean("linas-tuoa")
    //如果操作系统是windows 则给容器注册@Bean("bill-gaci")

    //传入一个Condition接口实例类,通过matches方法返回true或false
    @Conditional({WindowsCondition.class})
    @Bean("bill-gaci")
    public Person person1() {
        return new Person("比尔-盖茨", 60);
    }

    @Conditional(LinuxCondition.class)
    @Bean("linas-tuoa")
    public Person person2() {
        return new Person("林纳斯·托瓦兹", 60);
    }
}

条件类

public class WindowsCondition implements Condition {
    /**
     * 返回true则条件生效(该配置生效),返回false则条件不生效(配置失效)
     * @param conditionContext 判断条件能使用的上下文(环境)
     * @param annotatedTypeMetadata 注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //获取当前ioc使用的bean工厂
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        //获取到bean定义的注册类 。可以判断容器中bean的注册情况,也可以给容器注册bean
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        //判断当前容器中是否有该bean,传入需要判断的bean的id
        boolean person = registry.containsBeanDefinition("bill-gaci");
        System.out.println("++++"+person);
        //获取当前的操作系统
        String s = environment.getProperty("os.name");
        //如果当前时windows则返回true。代表该配置有效
        if (s.contains("Windows")) {
            return true;
        }
        return false;
    }
}

public class LinuxCondition implements Condition {
    /**
     * 返回true则条件生效(该配置生效),返回false则条件不生效(配置失效)
     * @param conditionContext 判断条件能使用的上下文(环境)
     * @param annotatedTypeMetadata 注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        //获取当前的操作系统
        String s = environment.getProperty("os.name");
        //如果当前时linux则返回true。代表该配置有效
        if (s.contains("linux")) {
            return true;
        }
        return false;
    }
}

测试

    @Test
    public void Test4(){
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig2.class);
        //获取当前ioc运行的环境,操作系统等属性
        Environment environment = annotation.getEnvironment();
        //获取操作系统
        String property = environment.getProperty("os.name");
        System.out.println("本操作系统:"+property);
        //获取所有的bean的id名称
        String[] beanDefinitionNames = annotation.getBeanDefinitionNames();
        for (String s : beanDefinitionNames) {
            System.out.println(s);
        }
        //获取该Bean的所有bean对象。
        //key为bean的id,value为初始化的对象
        Map<String, Person> beansOfType = annotation.getBeansOfType(Person.class);
        System.out.println(beansOfType);
    }

可通过启动参数配置本操作系统的虚拟变量 -Dos.name=linux

7、@Import导入组件

1)注册组件的三种方式
  • 包扫描(@ComponentScan)+组件标注注解(@Component,@Controller,@Service,@Repository,@Configuration)

  • @Bean【一般导入第三方里面的组件】

  • @Import【要导入到容器的组件,容器就会自动注册这个组件,bean的id默认为全类名

    • public class Red {}
      public class Green {}
      
    • @Configuration
      @Import({Red.class, Green.class})
      public class MyBeanConfig3 {
          @Bean
          public Person person() {
              return new Person("张三",11);
          }
      }
      
    •   @Test
          public void Test5(){
              ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig3.class);
              String[] beanDefinitionNames = annotation.getBeanDefinitionNames();
              //person
              //org.lc.custom.Red
        		//org.lc.custom.Green
              for (String s : beanDefinitionNames) {
                  System.out.println(s);
              }
          }
      
2)自定义导入bean的规则ImportSelector
public class White {}
public class Blue {}
/**
 * 自定义逻辑返回需要导入的组件
 */
public class MyImportSelector implements ImportSelector {
    /**
     * @param annotationMetadata 当前标注@Import注解上的所有注解信息
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //默认不能返回null,否则会抛异常
        //直接将要导入的组件以全类名写入到数组中
        return new String[]{"org.lc.custom.White","org.lc.custom.Blue"};
    }
}

@Configuration
@Import({Red.class, Green.class, MyImportSelector.class})
public class MyBeanConfig3 {
    @Bean
    public Person person() {
        return new Person("张三",11);
    }
}

    @Test
    public void Test5(){
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig3.class);
        String[] beanDefinitionNames = annotation.getBeanDefinitionNames();
        //org.lc.custom.Red
        // org.lc.custom.Green
        // org.lc.custom.White
        // org.lc.custom.Blue
        // person
        for (String s : beanDefinitionNames) {
            System.out.println(s);
        }
    }
3)判断bean手动注册bean的规则 ImportBeanDefinitionRegistrar
public class RainBow {}
public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
    /**
     *
     * @param importingClassMetadata 当前类的注解信息
     * @param registry 把所有需要添加到容器中的bean,调用BeanDefinitionRegistry.registerBeanDefinition手工注册进来
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //判断容器中是否有id为org.lc.custom.Green的bean实例
        boolean b = registry.containsBeanDefinition("org.lc.custom.Red");
        //判断容器中是否有id为org.lc.custom.Green的bean实例
        boolean b1 = registry.containsBeanDefinition("org.lc.custom.Green");
        //如果当前容器都有以上两个bean实例,就指定注册一个bean
        if (b && b1) {
            //得到一个bean的定义对象
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
            //指定注册一个bean,并指定bean的id为rainBow
            registry.registerBeanDefinition("rainBow",rootBeanDefinition);
        }
    }
}

@Configuration
//注册不分先后顺序,只要在这个Import注解中都认为按照指定规则可以注册到ioc中
@Import({Red.class, Green.class,MyImportSelector.class, MyImportBeanDefinitionRegister.class,})
public class MyBeanConfig3 {
    @Bean
    public Person person() {
        return new Person("张三",11);
    }
}
      @Test
    public void Test5(){
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig3.class);
        //org.lc.custom.Red
        // org.lc.custom.Green
        // org.lc.custom.White
        // org.lc.custom.Blue
        // person
        // rainBow
        String[] beanDefinitionNames = annotation.getBeanDefinitionNames();
        for (String s : beanDefinitionNames) {
            System.out.println(s);
        }
    }

8、FactoryBean工厂获取bean对象

public class Color {}
//创建Color的bean的工厂
public class MyFactoryBean implements FactoryBean<Color> {

    /**
     * 创建返回实例的bean对象
     * @return
     * @throws Exception
     */
    @Override
    public Color getObject() throws Exception {
        System.out.println("初始化bean");
        return new Color();
    }

    /**
     * 返回要创建bean实例的类型
     * @return 必须要和getObject()返回的类型对应
     */
    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    /**
     * 指定返回的bean是否为单例
     * @return true为单例,false为多例
     */
    @Override
    public boolean isSingleton() {
        return false;
    }
}
@Configuration
public class MyBeanConfig4 {

    /**
     * 注册工厂到ioc中
     * @return
     */
    @Bean
    public MyFactoryBean myFactoryBean() {
        return new MyFactoryBean();
    }
}
1)获取工厂中的bean对象

指定的bean为多例

①通过工厂的bean的id获取指定创建的bean对象

    @Test
    public void Test6(){
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig4.class);
        Object myFactoryBean = annotation.getBean("myFactoryBean");
        Object myFactoryBean1 = annotation.getBean("myFactoryBean");
        //初始化bean
        // 初始化bean
        // class org.lc.custom.Color
        // class org.lc.custom.Color
        System.out.println(myFactoryBean.getClass());
        System.out.println(myFactoryBean1.getClass());
    }

②直接通过指定创建的bean的类型获取

    @Test
    public void Test6(){
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig4.class);
        Object myFactoryBean = annotation.getBean(Color.class);
        Object myFactoryBean1 = annotation.getBean(Color.class);
        //初始化bean
        // 初始化bean
        // class org.lc.custom.Color
        // class org.lc.custom.Color
        System.out.println(myFactoryBean.getClass());
        System.out.println(myFactoryBean1.getClass());
    }
2)获取工厂bean对象

①直接通过工厂的类型获取

    @Test
    public void Test6(){
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig4.class);
        Object myFactoryBean = annotation.getBean(MyFactoryBean.class);
        Object myFactoryBean1 = annotation.getBean(MyFactoryBean.class);
        //class org.lc.custom.MyFactoryBean
        // class org.lc.custom.MyFactoryBean
        System.out.println(myFactoryBean.getClass());
        System.out.println(myFactoryBean1.getClass());
    }

②通过工厂bean的id前面加上&符号获取工厂bean对象

    @Test
    public void Test6(){
        ApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig4.class);
        Object myFactoryBean = annotation.getBean("&myFactoryBean");
        Object myFactoryBean1 = annotation.getBean("&myFactoryBean");
        //class org.lc.custom.MyFactoryBean
        // class org.lc.custom.MyFactoryBean
        System.out.println(myFactoryBean.getClass());
        System.out.println(myFactoryBean1.getClass());
    }

二、Bean的生命周期

①、通过以下三种方式指定bean的初始化方法和销毁方法:

1、使用Bean的initMethod和destroyMethod
public class Car {
    public Car() {
        System.out.println("构造器方法...");
    }
	
    public void init() {
        System.out.println("初始化方法...");
    }

    public void destory() {
        System.out.println("销毁方法...");
    }
}
1)单例bean的生命周期
  • 在容器创建的时候调用: bean的构造方法,完成属性赋值---->bean的初始化方法(initMethod)
  • 在容器close()的时候调用:bean的销毁方法(destroyMethod)
@Configuration
public class MyBeanConfig5 {
    //initMethod:指定bend的初始化方法
    //destroyMethod: 指定bean的销毁方法
    @Bean(initMethod = "init",destroyMethod = "destory")
    public Car car() {
        return new Car();
    }
}
    @Test
    public void Test7(){
        //创建容器
        AnnotationConfigApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig5.class);
        //关闭容器
        annotation.close();
    }
//初始化ioc容器时即调用
构造器方法...
初始化方法...
2)多例bean的生命周期
  • 在获取该bean时才调用: bean的构造方法,完成属性赋值---->bean的初始化方法(initMethod)
  • 在容器close时,bean的销毁方法不由IOC容器控制
@Configuration
public class MyBeanConfig5 {
    //initMethod:指定bend的初始化方法
    //destroyMethod: 指定bean的销毁方法
    @Scope("prototype")
    @Bean(initMethod = "init",destroyMethod = "destory")
    public Car car() {
        return new Car();
    }
}
    @Test
    public void Test7(){
        //创建容器
        AnnotationConfigApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig5.class);
        //在多例bean时,获取bean时才会调用构造方法和初始化方法
        annotation.getBean("car");
        //关闭容器,多例对象的销毁不由IOC控制。
        annotation.close();
    }
//获取bean时才调用
构造器方法...
初始化方法...
2、实现InitializingBean和DisposableBean接口

我们可以通过实现InitializingBean和DisposableBean接口来实现bean的初始化方法构造方法

  • 单例
    • 在容器启动的时,调用bean的构造器方法,并完成属性赋值,再调用 afterPropertiesSet初始化方法
    • ioc容器close时,调用destroy方法
public class Cat implements InitializingBean, DisposableBean {

    public Cat() {
        System.out.println("Cat 构造方法...");
    }

    /**
     * bean创建完成并且里面的属性赋值完毕就会调用 (相当于初始化方法)
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Cat afterPropertiesSet...");
    }

    /**
     * bran销毁调用的方法
     * @throws Exception
     */
    @Override
    public void destroy() throws Exception {
        System.out.println("Cat destory...");
    }
}

@Configuration
public class MyBeanConfig5 {
    @Bean
    public Cat cat() {
        return new Cat();
    }
}
    @Test
    public void Test7(){
        //创建容器
        AnnotationConfigApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig5.class);
        //关闭容器,多例对象的销毁不由IOC控制。
        annotation.close();
    }
Cat 构造方法...
Cat afterPropertiesSet...
Cat destory...
3)通过@PostConstruct和@PreDestroy
  • 单例
    • IOC创建调用构造器方法并完成属性赋值,然后调用被 @PostConstruct标有的初始化方法
    • IOC调用close时调用 被@PreDestroy标有的销毁方法
public class Pig {

    public Pig() {
        System.out.println("pig 构造方法...");
    }

    /**
     * 构造器创建并完成属性赋值调用init方法
     */
    @PostConstruct
    public void init() {
        System.out.println("pig 初始化方法...");
    }

    /**
     * 容器移除之前调用的方法
     */
    @PreDestroy
    public void destory() {
        System.out.println("pig 销毁方法");
    }
}
@Configuration
public class MyBeanConfig5 {
    @Bean
    public Pig pig() {
        return new Pig();
    }
}
pig 构造方法...
pig 初始化方法...
pig 销毁方法

②Bean的前后置处理器BeanPostProcessor接口

  • BeanPostProcessor对所有bean的init初始化方法进行管理,在初始化方法前调用BeanPostProcessor的postProcessBeforeInitialization前置处理器,在初始化方法完成后调用BeanPostProcessor的postProcessAfterInitialization后置处理器。

bean实例

public class Pig {

    public Pig() {
        System.out.println("pig 构造方法...");
    }

    /**
     * 构造器创建并完成属性赋值调用init方法
     */
    @PostConstruct
    public void init() {
        System.out.println("pig 初始化方法...");
    }

    /**
     * 容器移除之前调用的方法
     */
    @PreDestroy
    public void destory() {
        System.out.println("pig 销毁方法");
    }
}

bean的前后置处理器

public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * 在调用bean值的init初始化方法之前调用postProcessBeforeInitialization前置处理器
     * @param bean 当前创建的bean
     * @param beanName bean的id
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean的init前置处理器方法");
        System.out.println("bean:"+bean);
        System.out.println("beanName:"+beanName);
        return bean;
    }

    /**
     * 在调用bean值的init初始化方法之后调用postProcessAfterInitialization后置处理器
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean的init后置处理器方法");
        return bean;
    }
}

注册到容器

@Configuration
public class MyBeanConfig5 {
	//注册bean实例
    @Bean
    public Pig pig() {
        return new Pig();
    }
	//注册bean的前置后置处理器
    @Bean
    public MyBeanPostProcessor myBeanPostProcessor() {
        return new MyBeanPostProcessor();
    }
}

测试

  @Test
    public void Test7(){
        //创建容器
        AnnotationConfigApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig5.class);
        //关闭容器,多例对象的销毁不由IOC控制。
        annotation.close();
    }

结果

//调用bean的构造方法
pig 构造方法...

//调用bean的前置处理器
bean的init前置处理器方法
bean:org.lc.custom.Pig@63a65a25
beanName:pig
 
//调用bean指定的init初始化方法
pig 初始化方法...

//调用bean的后缀处理器方法
bean的init后置处理器方法
   
//调用bean指定的destory销毁方法
pig 销毁方法

三、属性赋值

1、@Value

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    @Value("张三")
    private String name;
    //直接写SPEL表达式
    @Value("#{20-5}")
    private Integer age;
}
@Configuration
public class MyBeanConfig5 {
    @Bean
    public Person person() {
        return new Person();
    }
}
    @Test
    public void Test7(){
        AnnotationConfigApplicationContext annotation = new AnnotationConfigApplicationContext(MyBeanConfig5.class);
        Person person = annotation.getBean(Person.class);
        //Person(name=张三, age=15)
        System.out.println(person);
    }

2、@PropertySource 导入properties 配置文件

db.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
jdbc.username=root
jdbc.password=123456
@ToString
@Getter
@Setter
public class DbDataSource {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
}
@Configuration
//导入类路径下的db.properties配置到IOC中
@PropertySource({"classpath:db.properties"})
public class SpringConfig1 {
    @Bean
    public DbDataSource dbDataSource() {
        return new DbDataSource();
    }
}
    @Test
    public void Test8(){
        AnnotationConfigApplicationContext annotation = new AnnotationConfigApplicationContext(SpringConfig1.class);
        ConfigurableEnvironment environment = annotation.getEnvironment();
        String property = environment.getProperty("jdbc.url");
        //从环境中获取:jdbc:mysql://localhost:3306/test?serverTimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
        System.out.println("从环境中获取:"+property);
        DbDataSource bean = annotation.getBean(DbDataSource.class);
        //DbDataSource(driver=com.mysql.jdbc.Driver, url=jdbc:mysql://localhost:3306/test?serverTimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false, username=root, password=123456)
        System.out.println(bean);
    }

四、自动装配

1、@Autowired @Qualifier

  • @Autowired

    @Autowired是Spring的注解.只能在spring框架中使用

    • 首先按照注入的类型(UserService)去IOC容器中查找对应的组件,若找到则装配,找不到抛异常
    • 若找到多个,则按照注入的属性名称(userService)作为bean的id去容器中查找,找到则装配,找不到,抛异常
  • @Qualifier

    • 若使用**@Autowired找到多个类型,注入时不想通过默认属性名作为id寻找,那么直接可以使用@Qualifier**按照其指定的名称去容器中找知道的bean
    • 若指定**@Qualifier还找不到,则定义@Autowired(required = false),该属性装配为null**。

2、@Resource

J2EE的注解. 扩展性更强,可以用在其他框架上

  • name和type都不指定,即按照默认按照名称来装配注入,若找不到则按照bean的类型来找,若找到多个bean类型则抛出异常
  • 它有两个属性是比较重要的:
    • name: Spring将name的属性值解析为bean的名称, 使用byName的自动注入策略, 该name必须存在,否则编译的时候就会提示错误,启动则抛异常
    • type: Spring将type的属性值解析为bean的类型, 使用byType的自动注入策略,若找到多个类型,则按照名称属性取找,找不到则抛出异常。
    • 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

3、@Primary

@Configuration
public class SpringConfig1 {
    @Bean
    public EmployeeController employeeController() {
        return new EmployeeController();
    }
	//当@Autowired不指定名称,按照默认规则进行注入时,会注入加@Primary的bean
    @Primary
    @Bean
    public EmployeeService employeeService1() {
        return new EmployeeService();
    }
    @Bean
    public EmployeeService employeeService2() {
        return new EmployeeService();
    }
}
@Controller
public class EmployeeController {
    //默认装配加@Primary的bean
    @Autowired
    private EmployeeService employeeServiceOne;

    //指定其他的bean
    @Qualifier("employeeService2")
    @Autowired
    private EmployeeService employeeServiceTwo;
}

4、@Profile 指定组件运行环境

  • 标注在bean上,只有这个环境被激活的时候才能注册到容器中。默认是default环境
  • 标注在配置类上,只有这个环境被激活的时候该配置类注册到容器中
  • 若没有标注在任何bean或配置类上,则该bean和配置类都是加载到容器中的

指定环境参数

  • 使用命令行动态参数指定环境 -Dspring.profiles.active=test

  • 使用代码的方式激活环境

            AnnotationConfigApplicationContext annotation = new AnnotationConfigApplicationContext();
            //设置需要激活的环境 这里设置为pro
            annotation.getEnvironment().setActiveProfiles("pro");
            //加载主配置类
            annotation.register(SpringConfig2.class);
            //启动刷新新容器
            annotation.refresh();
    
多数据源示例

db.properties

jdbc.driver=com.mysql.jdbc.Driver
#测试地址
jdbc.url_test=jdbc:mysql://localhost:3306/test?serverTimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
#开发地址
jdbc.url_dev=jdbc:mysql://localhost:3306/hcg?serverTimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
#生产地址
jdbc.url_pro=jdbc:mysql://localhost:3306/hcgnew?serverTimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
jdbc.username=root
jdbc.password=123456
@ToString
@Getter
@Setter
public class DbDataSource {
    @Value("${jdbc.driver}")
    private String driver;

    //测试地址
    @Value("${jdbc.url_test}")
    private String url_test;
    //开发地址
    @Value("${jdbc.url_dev}")
    private String url_dev;
    //生产地址
    @Value("${jdbc.url_pro}")
    private String url_pro;

    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
}
@Configuration
@PropertySource({"classpath:db.properties"})
public class SpringConfig2 {

    //存放地址信息的数据源
    @Bean
    public DbDataSource dbDataSource() {
        return new DbDataSource();
    }

    //测试环境数据源
    @Profile("test")
    @Bean
    public DruidDataSource druidDataSourceTest() {
        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setDriverClassName(dbDataSource().getDriver());
        druidDataSource.setUrl(dbDataSource().getUrl_test());
        druidDataSource.setUsername(dbDataSource().getUsername());
        druidDataSource.setPassword(dbDataSource().getPassword());
        return druidDataSource;
    }

    //开发环境数据源
    @Profile("dev")
    @Bean
    public DruidDataSource druidDataSourceDev() {
        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setDriverClassName(dbDataSource().getDriver());
        druidDataSource.setUrl(dbDataSource().getUrl_dev());
        druidDataSource.setUsername(dbDataSource().getUsername());
        druidDataSource.setPassword(dbDataSource().getPassword());
        return druidDataSource;
    }

    //生产环境数据源
    @Profile("pro")
    @Bean
    public DruidDataSource druidDataSourcePro() {
        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setDriverClassName(dbDataSource().getDriver());
        druidDataSource.setUrl(dbDataSource().getUrl_pro());
        druidDataSource.setUsername(dbDataSource().getUsername());
        druidDataSource.setPassword(dbDataSource().getPassword());
        return druidDataSource;
    }
}

    @Test
    public void Test10(){
        AnnotationConfigApplicationContext annotation = new AnnotationConfigApplicationContext();
        //设置需要激活的环境 这里设置为pro
        annotation.getEnvironment().setActiveProfiles("pro");
        //加载主配置类
        annotation.register(SpringConfig2.class);
        //启动刷新新容器
        annotation.refresh();

        //获取所有IOC中bean的名称
        String[] beanDefinitionNames = annotation.getBeanDefinitionNames();
        for (String s : beanDefinitionNames) {
            System.out.println(s);
            if (s.contains("druidDataSource")) {
                DruidDataSource bean = (DruidDataSource) annotation.getBean(s);
                System.out.println(s+"的url为:"+bean.getUrl());
            }
        }
    }
druidDataSourcePro
druidDataSourcePro的url为:jdbc:mysql://localhost:3306/hcgnew?serverTimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false

五、AOP配置

基于注解的AOP配置流程

1)目标类
  • 如果目标类不实现接口,则从容器中获取Caculate对象时使用的是CGLIB代理,基于子类的代理

  • 如果目标类实现接口,则需要从容器中获取目标类的接口对象来调用,使用的是jdk动态代理,基于接口的代理

public class Caculate {
    public int add(int a, int b) {
        return a+b;
    }
    public int substract(int a, int b) {
        return a-b;
    }
    public int multiply(int a, int b) {
        return a-b;
    }
    public int divide(int a, int b) {
        return a/b;
    }
}
2)切面配置,指定切面
  • @Aspect指定切面类
@Aspect
public class LogUtils {

    //指定切点表达式
    @Pointcut("execution(public * org.lc.aop.Caculate.*(..))")
    public void pointCut(){ }

    //指定前置通知
    @Before("pointCut()")
    public void startLog(JoinPoint joinPoint){
        //获取方法对象
        Signature signature = joinPoint.getSignature();
        //获取方法参数
        Object[] args = joinPoint.getArgs();
        System.out.println("执行:【" + signature.getName() + "】方法之前的记录,方法的参数列表为【"+ Arrays.toString(args)+"】");
    }

    //返回通知(方法正常返回) 并指定方法返回的结果
    @AfterReturning(value = "pointCut()",returning = "object")
    public void afterReturnLog(JoinPoint joinPoint,Object object) {
        System.out.println("执行:【" + joinPoint.getSignature().getName() + "】方法之后的记录,方法的结果为【" + object + "】");
    }

    //异常通知,并指定异常类型
    @AfterThrowing(value = "pointCut()", throwing = "e")
    public void exceptionLog(JoinPoint joinPoint, Exception e) {
        System.out.println("【"+joinPoint.getSignature().getName()+"】方法抛出的异常为:【"+e+"】");
    }

    //后置通知(无论如何都会执行)
    @After("pointCut()")
    public void afterLog(JoinPoint joinPoint) {
        System.out.println("方法【"+joinPoint.getSignature().getName()+"】总要执行的记录....");
    }

}
3)主配置文件
  • @EnableAspectJAutoProxy开启基于注解的aop配置
//@EnableAspectJAutoProxy开启基于注解的aop配置
@EnableAspectJAutoProxy
@Configuration
public class AopConfig {

    //注册目标类到容器
    @Bean
    public Caculate caculate() {
        return new Caculate();
    }

    //注册切面类
    @Bean
    public LogUtils logUtils() {
        return new LogUti4ls();
    }

}
4)测试
    @Test
    public void Test1(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
        Caculate bean = applicationContext.getBean(Caculate.class);
        int add = bean.add(1, 2);
        System.out.println(add);
    }
执行:【add】方法之前的记录,方法的参数列表为【[1, 2]】
执行:【add】方法之后的记录,方法的结果为【3】
方法【add】总要执行的记录....
3

六、事务配置

基于注解的事务配置流程

1)主配置文件
  • @EnableTransactionManagement 开启基于注解的事务管理
  • PlatformTransactionManager 配置事务管理器
//扫描包
@ComponentScan(basePackages = "org.lc.tx")
//开启基于注解的事务管理
@EnableTransactionManagement
@Configuration
public class TranscationConfig {

    //注入druid数据源
    @Bean
    public DruidDataSource druidDataSource() {
        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/test?serverTimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        return druidDataSource;
    }

    //注入操作数据库的jdbc并指定数据源。
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(druidDataSource());
    }

    //配置事务管理器
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(druidDataSource());
    }
}
2)service/dao层

dao

@Repository
public class BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 减去某个用户的余额
     * @param username
     * @param money
     */
    public void updataBalance(String username, int money) {
        String sql = "update account set balance=balance-? where username=?";
        jdbcTemplate.update(sql, money, username);
    }

    /**
     * 按照ISBN号码获取图书的价格
     * @param isbn
     * @return
     */
    public int getPrice(String isbn) {
        String sql = "SELECT price from book where isbn=?";
        return jdbcTemplate.queryForObject(sql,Integer.class, isbn);
    }

    /**
     * 按照ISBN号码减去库存
     * 这里我们设置每次减1
     * @param isbn
     */
    public void updateStock(String isbn) {
        String sql = "update book_stock set stock=stock-1 where isbn=?";
        jdbcTemplate.update(sql, isbn);
    }
}

service

@Service
public class BookService {

    @Autowired
    private BookDao bookDao;

    /**
     * 结账
     * @param username
     * @param isbn
     */
    //在需要回滚的业务方法上加@Transactional注解
    // @Transactional
    public void checkout(String username, String isbn) {
        //减库存
        bookDao.updateStock(isbn);
        //查询图书价格
        int price = bookDao.getPrice(isbn);
        //减余额
        bookDao.updataBalance(username, price);
        //模拟运行时异常
        // int i=1/0;
    }
}
3)测试
    @Test
    public void Test2(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TranscationConfig.class);
        BookService bookService = applicationContext.getBean(BookService.class);
        bookService.checkout("Tom","ISBN-001");
        System.out.println("结账成功!");
    }

七、基于注解的SSM整合

1、pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>springmvc_annotation</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>springmvc_annotation Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>
  </dependencies>
</project>

2、Spring配置

//spring不扫描@Controller容器
@ComponentScan(value = "org.lc",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class SpringRootConfig {
}

3、SpringMVC配置

我们可以实现WebMvcConfigurer接口的对应方法实现对SpringMVC的一些配置

//springmvc只扫描controller
@ComponentScan(value = "org.lc",useDefaultFilters = false,includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
//开启基于注解的sprignmvc配置 相当于<mvc:annotation-driven/>
@EnableWebMvc
public class SpringMVCAppConfig implements WebMvcConfigurer{

    //配置视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //配置jsp的视图解析器
        registry.jsp("/WEB-INF/pages/", ".jsp");
    }

    //处理静态资源 相当于    <mvc:default-servlet-handler/>
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
         configurer.enable();
    }

    //配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截任意请求
        registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
    }
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {

    }
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

    }
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {

    }
    @Override
    public void addFormatters(FormatterRegistry registry) {

    }
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }
    @Override
    public void addCorsMappings(CorsRegistry registry) {

    }
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {

    }
    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {

    }
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

    }
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

    }
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {

    }
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {

    }
    @Override
    public Validator getValidator() {
        return null;
    }
    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}
1)配置拦截器
public class MyFirstInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyFirstInterceptor....preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyFirstInterceptor....postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyFirstInterceptor....afterCompletion");
    }
}

4、初始化前端控制器和Spring容器

替代web.xml文件

//web容器启动的时候创建对象,调用方法来创建初始化容器以前的前端控制器
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 获取根容器的配置类;(spring的配置文件) 父容器
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringRootConfig.class};
    }

    /**
     * 获取web容器的配置类 (springmvc的配置文件)  子容器
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMVCAppConfig.class};
    }

    /**
     * 获取DispatcherServlet的映射信息
     *    / 代表拦截所有请求(包括静态资源( .js, .png,.html等)),但不拦截.jsp
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        //添加拦截规则
        return new String[]{"/"};
    }
}

5、测试

1)controller/service

service

@Service
public class HelloService {
    public String hello() {
        return "hello...";
    }
}

controller

@Controller
@RequestMapping("/")
public class HelloController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String sayHello() {
        String hello = helloService.hello();
        return hello;
    }

    @GetMapping("/hello1")
    public String sayHello1() {
        return "ccess";
    }
}

八、SpringMVC异步处理

1、返回Callable

拦截器

public class MyFirstInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyFirstInterceptor....preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyFirstInterceptor....postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyFirstInterceptor....afterCompletion");
    }
}

控制器测试

@Controller
public class AsyncController {
    /**
     * 1、控制器返回Callable
     * 2、Spirng异步处理,将Callable提交到TaskExecutor,使用一个隔离的线程来执行
     * 3、DispatcherServlet的所有的Filter退出web容器的线程,但是response保持打开状态
     * 4、Callable返回结果,SpringMvc将请求重新派发给容器,恢复之前的处理
     * 5、根据Callable返回的结果,SpringMvc继续进行视图渲染流程等(从收请求--视图渲染)
     * @return
     */
    @ResponseBody
    @GetMapping("/async")
    public Callable<String> async() {
        System.out.println("主线程开始:"+Thread.currentThread()+"==>时间为:"+System.currentTimeMillis());
        Callable<String> callable = () -> {
            System.out.println("副线程开始:"+Thread.currentThread()+"==>时间为:"+System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println("副线程结束:"+Thread.currentThread()+"==>时间为:"+System.currentTimeMillis());
           return "Callable<String> async()";
        };
        System.out.println("主线程结束:"+Thread.currentThread()+"==>时间为:"+System.currentTimeMillis());
        return callable;
    }
}
// 第一次拦截的请求  http://localhost:8080/async 
MyFirstInterceptor....preHandle
主线程开始:Thread[http-bio-8080-exec-7,5,main]==>时间为:1595580711963
主线程结束:Thread[http-bio-8080-exec-7,5,main]==>时间为:1595580711963
//--------------- DispatcherServlet的所有的Filter退出web容器的线程---------

//------------------等待callable执行-----
副线程开始:Thread[MvcAsync1,5,main]==>时间为:1595580711969
副线程结束:Thread[MvcAsync1,5,main]==>时间为:1595580713970
////------------------callable执行完成-----
    
//拦截器再次受到 请求    
MyFirstInterceptor....preHandle
//callable之前的返回值就是目标方法的返回值。    
MyFirstInterceptor....postHandle
MyFirstInterceptor....afterCompletion

如果时异步的情况下并不能真正的拦截到业务逻辑,需要实现AsyncHandlerInterceptor接口

2、返回DeferredResult

队列保存DeferredResult<Object>

public class DeferredResultQueue {

    //使用队列存储DeferredResult<Object>
    private static Queue<DeferredResult<Object>> queue=new ConcurrentLinkedQueue<>();

    //向队列中存储方法
    public static void save(DeferredResult<Object> deferredResult) {
        queue.add(deferredResult);
    }

    //向队列中取的方法
    public static DeferredResult<Object> get() {
        return queue.poll();
    }
}

控制器

@Controller
public class AsyncController1 {

    @ResponseBody
    @GetMapping("/creatOrder")
    public DeferredResult<Object> createOrder() {
        //监听该线程必须3秒内完成,否则会失败
        DeferredResult<Object> deferredResult = new DeferredResult<>((long) 3000, "create fail");
        DeferredResultQueue.save(deferredResult);
        return deferredResult;
    }

    @ResponseBody
    @GetMapping("/create")
    public String create() {
        String s = UUID.randomUUID().toString();
        DeferredResult<Object> deferredResult = DeferredResultQueue.get();
        //设置订单结果。
        deferredResult.setResult(s);
        return "success:"+s;
    }
}

  • 首先请求 http://localhost:8080/creatOrder 将DeferredResult放入到队列监听,监听的超时时间为3s,超出实现则抛出错误信息,此时为阻塞状态
  • 在3s内去请求 http://localhost:8080/create 创建订单信息869c40b1-25fd-4c48-b40d-f8809b1226aa,只要给DeferredResult设置了setResult(s)结果,那么监听的DeferredResult<Object>就会返回设置的结果。然后阻塞的creatOrder请求得到相应869c40b1-25fd-4c48-b40d-f8809b1226aa

九、其它注解解释

@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd")

注解@JsonFormat主要是后台到前台的时间格式的转换

注解@DataFormAT主要是前后到后台的时间格式的转换

	@DateTimeFormat(pattern = "yyyy-MM-dd")    
	@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
    private Date birthday;
@configuration和@component的区别

总结:@configuration+@Bean(注入的单例)

@component+@Bean(注入的多例)

https://blog.csdn.net/long476964/article/details/80626930open in new window

@Configuration是随容器启动开始加载的,始终存在的单例模式。 @Component中的bean使用一次即实例化一次

@configuration使用cglid动态代理==>

@Configuration
public class MyBeanConfig {
 
    @Bean
    public Country country(){
        return new Country();
    }
 
    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }
}

直接调用 country() 方法返回的是同一个实例

@Component 注解并没有通过 cglib 来代理@Bean 方法的调用==>

@Component
public class MyBeanConfig {
 
    @Bean
    public Country country(){
        return new Country();
    }
 
    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }
}

每次调用country(),每次都会创建一个实例

@PropertySource("classpath:person.properties")

导入properties文件

@ImportResource(locations = "classpath:beans.xml")

导入配置的xml文件

@Bean @scope("singleton")

在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例)

singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。

prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。

Spring bean 默认是单例模式。

1.singleton单例模式,

  全局有且仅有一个实例

2.prototype原型模式,

  每次获取Bean的时候会有一个新的实例

3.request  

        request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,

4.session 

         session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效

5.global session

global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个 portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。如果你在web中使用global session作用域来标识bean,那么web会自动当成session类型来使用。
容器的单例和多例

@Component注解默认实例化的对象是单例,如果想声明成多例对象可以使用@Scope("prototype")

@Repository默认单例

@Service默认单例

@Controller默认多例

@order(100)

默认为最大值2^31-1,即int的范围。

默认最大值表示优先级最低,即执行的顺序

值越大优先级越低,反之值越小优先级越高

@RequestBody @RequestParam

@RequestBody==>

顾明思意,请求必须是以请求体的形式接受参数

@RequestParam==>

以url传参

@import(configuration.class)

导入java的配置文件 被@Configuration标记的文件

@ConfigurationProperties(prefix = "person")

yaml,properties文件属性配置注入

@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")

@ConditionalOnProperty来控制Configuration是否生效

通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。 如果该值为空,则返回false; 如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。如果返回值为false,则该configuration不生效;为true则生效。

swagger.enable=true
@JsonIgnore

忽视该字段json的序列化