Spring注解驱动总结笔记

原始链接:https://liayun.blog.csdn.net/article/details/115053350

有用的方法

/**
 * 获取获取注解生成的bean
 * */
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = applicationContext.getBean(Person.class);

/**
 * 获取指定bean名称
 * */
        String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
        for (String name : namesForType) {
            System.out.println(name);
        }
    
/**
    * 获取容器中所有bean的名称
    */
    String[] definitionNames = applicationContext.getBeanDefinitionNames();
    for (String name : definitionNames) {
            System.out.println(name);
    }
    
    /**
    * 获取容器中指定bean类的map集合
    */
    Map<String, Person> persons = applicationContext.getBeansOfType(Person.class); // 找到这个Person类型的所有bean
    System.out.println(persons);
    

IOC类型

1.组件的注册

@Configuration和@Bean给容器中注册组件

@Configuration // 告诉Spring这是一个配置类
public class MainConfig {

    // @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
    //bean在IOC容器中的名称就是使用@Bean注解标注的方法名称,即该实例中bean的名称为person
    @Bean
    public Person person() {
        return new Person("liayun", 20);
    }
    
}

2.注解包扫描

@ComponentScan

@Filter

  • type属性:

    FilterType.ANNOTATION:按照注解进行包含或者排除

    FilterType.ASSIGNABLE_TYPE:按照给定的类型进行包含或者排除;类、接口、实现类、子接口

    FilterType.ASPECTJ:按照ASPECTJ表达式进行包含或者排除(不常用)

    FilterType.REGEX:按照正则表达式进行包含或者排除(不常用)

    FilterType.CUSTOM:按照自定义规则进行包含或者排除(使用该枚举需要有一个实现TypeFilter的类,返回false标识不匹配,true标识匹配)

// 这个配置类也是一个组件 
@ComponentScan(value="com.meimeixia") // value指定要扫描的包
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {

    // @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
    @Bean("person")
    public Person person01() {
        return new Person("liayun", 20);
    }
    
}



// 带过滤器的包扫描
//excludeFilters:排除扫描的特定包
@ComponentScan(value="com.meimeixia",excludeFilters={
        /*
         * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
         * classes:除了@Controller和@Service标注的组件之外,IOC容器中剩下的组件我都要,即相当于是我要排除@Controller和@Service这俩注解标注的组件。
         */
        @Filter(type=FilterType.ANNOTATION, classes={Controller.class, Service.class})
}) // value指定要扫描的包
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {

    // @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
    @Bean("person")
    public Person person01() {
        return new Person("liayun", 20);
    }
    
}

//includeFilters,禁用掉默认的过滤器
@ComponentScan(value="com.meimeixia", includeFilters={
        /*
         * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
         * classes:我们需要Spring在扫描时,只包含@Controller注解标注的类
         */
        @Filter(type=FilterType.ANNOTATION, classes={Controller.class})
}, useDefaultFilters=false) // value指定要扫描的包



3.设置组件的作用域

@Scope属性

  • singleton:表示组件在spring容器中是单实例,是spring的默认值

    【spring容器启动时,将单实例组件实例化后会立即加载到spring容器中】

  • prototype:表示组件在spring容器中是多实例

    【ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象】

  • request:每次请求都会创建一个新的实例对象,但是同一个请求只能创建一个实例对象,作用域只在spring容器的web容器中

  • session:在同一个session范围内,创建一个新的实例对象,作用域在web容器中

  • application:全局web应用级别的作用域,使用于web环境中,且在web应用有多个spring容器的情况下也只能使用一个同名bean


@Configuration
public class MainConfig2 {
    
    @Scope("prototype") // 通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
    @Bean("person")
    public Person person() {
        return new Person("美美侠", 25);
    }
    
}

4.懒加载机制

@Lazy

  • @Lazy注解会使得实例化的对象在Spring容器启动的时候并不会加载,而是在第一次使用此bean的时候才会加载,但当你多次获取bean的时候并不会重复加载,只是在第一次获取的时候才会加载,这不是延迟加载的特性,而是单实例bean的特性【配合单实例使用】

5.按照一定的条件注册bean

@Conditional

  • 不仅可以添加到类上(Element.TYPE),也可以添加到方法上(Element.METHOD)
  • 内含一个继承Condition或者子类型的class对象数组
  • Condition是一个接口,需要写一个类实现该接口,并制定匹配原则,返回为true时表示满足实例化条件,false表示不满足实例化条件,带有@Conditional的方法/类会根据实现类制定的匹配规则被ioc容器选择性注册或者不注册
  • matches方法中的ConditionContext.getRegistry()返回一个BeanDefinitionRegistry,能获取spring容器中的所有bean
    • registerBeanDefinition():向spring容器注册bean对象
    • removeBeanDefinition():从spring容器中移除一个bean对象
    • getBeanDefinition():查看某个bean的定义信息
    • containsBeanDefinition():查看是否包含某个bean
package com.meimeixia.condition;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
* 判断操作系统是否是Linux系统
* @author liayun
*
*/
public class LinuxCondition implements Condition {

    /**
    * ConditionContext:判断条件能使用的上下文(环境)
    * AnnotatedTypeMetadata:当前标注了@Conditional注解的注释信息
    */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 判断操作系统是否是Linux系统
        
        // 1. 获取到bean的创建工厂(能获取到IOC容器使用到的BeanFactory,它就是创建对象以及进行装配的工厂)
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 2. 获取到类加载器
        ClassLoader classLoader = context.getClassLoader();
        // 3. 获取当前环境信息,它里面就封装了我们这个当前运行时的一些信息,包括环境变量,以及包括虚拟机的一些变量
        Environment environment = context.getEnvironment();
        // 4. 获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();
        
        // 在这儿还可以做更多的判断,比如说我判断一下Spring容器中是不是包含有某一个bean,就像下面这样,如果Spring容器中果真包含有名称为person的bean,那么就做些什么事情...
        boolean definition = registry.containsBeanDefinition("person");

        String property = environment.getProperty("os.name");
        if (property.contains("linux")) {
            return true;
        }
        
        return false;
    }

}

package com.meimeixia.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

import com.meimeixia.bean.Person;
import com.meimeixia.condition.LinuxCondition;
import com.meimeixia.condition.WindowsCondition;

@Configuration
public class MainConfig2 {
    
    @Lazy
    @Bean("person")
    public Person person() {
        System.out.println("给容器中添加咱们这个Person对象...");
        return new Person("美美侠", 25);
    }

    @Conditional({WindowsCondition.class})
    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates", 62);
    }
    
    @Conditional({LinuxCondition.class})
    @Bean("linus")
    public Person person02() {
        return new Person("linus", 48);
    }
    
}

6.给容器快速导入一个组件

@import

  • 只允许放到类上面(Element.TYPE)
  • 属性:class类型数组
  • 可以配合@Configuration、ImportSelector、ImportBeanDefinitionRegistrar使用
  • 使用方式
    • 直接填写class数组以导入类
      
      // 对配置类中的组件进行统一设置
      @Conditional({WindowsCondition.class}) // 满足当前条件,这个类中配置的所有bean注册才能生效
      @Configuration
      @Import(Color.class) // @Import快速地导入组件,id默认是组件的全类名
      public class MainConfig2 {
          
          @Lazy
          @Bean("person")
          public Person person() {
              System.out.println("给容器中添加咱们这个Person对象...");
              return new Person("美美侠", 25);
          }
          
          @Bean("bill")
          public Person person01() {
              return new Person("Bill Gates", 62);
          }
          
          @Conditional({LinuxCondition.class})
          @Bean("linus")
          public Person person02() {
              return new Person("linus", 48);
          }
          
          
          /**
           * 
           * 结果:把color类导入进spring容器中
           * 
           * */
          
      }
      
      
      /******************************************************/
      public class Color {
      
      }
      
      
      
    • importSelector接口,即批量导入
      • 方法:String[] selectImports(AnnotationMetadata data);
        • 返回值表示需要导入的类(全限定类名)
        • AnnotationMetadata:当前标注@Import注解的类的所有注解信息,也就是说不仅能获取到@Import注解里面的信息,还能获取到其他注解的信息
        • 
          /**
           * 自定义逻辑,返回需要导入的组件
           * @author liayun
           *
           */
          public class MyImportSelector implements ImportSelector {
          
              // 返回值:就是要导入到容器中的组件的全类名
              // AnnotationMetadata:当前标注@Import注解的类的所有注解信息,也就是说不仅能获取到@Import注解里面的信息,还能获取到其他注解的信息
              @Override
              public String[] selectImports(AnnotationMetadata importingClassMetadata) { // 在这一行打个断点,debug调试一下
                  
                  // 方法不要返回null值,否则会报空指针异常
          //        return new String[]{}; // 可以返回一个空数组
                  return new String[]{"com.meimeixia.bean.Bule", "com.meimeixia.bean.Yellow"};
              }
          
          }
          
          /*********************************************/
          
          
          // 对配置类中的组件进行统一设置
          @Conditional({WindowsCondition.class}) // 满足当前条件,这个类中配置的所有bean注册才能生效
          @Configuration
          @Import({MyImportSelector.class}) // @Import快速地导入组件,id默认是组件的全类名
          public class MainConfig2 {
          
              @Lazy
              @Bean("person")
              public Person person() {
                  System.out.println("给容器中添加咱们这个Person对象...");
                  return new Person("美美侠", 25);
              }
          
              @Bean("bill")
              public Person person01() {
                  return new Person("Bill Gates", 62);
              }
              
              @Conditional({LinuxCondition.class})
              @Bean("linus")
              public Person person02() {
                  return new Person("linus", 48);
              }
              
          }
          
          
          
    • ImportBeanDefinitionRegistrar接口方式,即手工注册bean到容器中
      • 方法:registerBeanDefinitions(AnnotationMetadata data,BeanDefinitionRegistry registry);//spring官方动态注册bean所使用的接口
      • 所有实现了该接口的类都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其他的bean初始化的,也能被aop、validator等机制处理。
      • 需要配合@Configuration和@Import这俩注解,其中@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类,@Configuration注解spring配置文件
      • 
        
        public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        
            /**
             * AnnotationMetadata:当前类的注解信息
             * BeanDefinitionRegistry:BeanDefinition注册类
             * 
             * 我们可以通过调用BeanDefinitionRegistry接口中的registerBeanDefinition方法,手动注册所有需要添加到容器中的bean
             */
            @Override
            public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
                
                    // RootBeanDefinition是BeanDefinition接口的一个实现类
                    RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); // bean的定义信息
                    // 注册一个bean,并且指定bean的名称
                    registry.registerBeanDefinition("rainBow", beanDefinition);
            }
        
        }
        
        
        /************************************************/
        package com.meimeixia.bean;
        
        public class RainBow {
        
        }
        /*************************************************/
        package com.meimeixia.config;
        
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Conditional;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.context.annotation.Import;
        import org.springframework.context.annotation.Lazy;
        
        import com.meimeixia.bean.Color;
        import com.meimeixia.bean.Person;
        import com.meimeixia.bean.Red;
        import com.meimeixia.condition.LinuxCondition;
        import com.meimeixia.condition.MyImportBeanDefinitionRegistrar;
        import com.meimeixia.condition.MyImportSelector;
        import com.meimeixia.condition.WindowsCondition;
        
        // 对配置类中的组件进行统一设置
        @Configuration
        @Import({MyImportBeanDefinitionRegistrar.class}) // @Import快速地导入组件,id默认是组件的全类名
        public class MainConfig2 {
            
        }
        
        
        

7.使用FactoryBean向Spring容器中注册Bean

  • 为什么需要FactoryBean的注册方式:
    • 注册的Bean涉及到复杂的逻辑(一系列的if-else操作、同步操作、代理操作等),使用配置文件导致灵活性严重不足
    • 生产一种类型的bean,在创建的过程中再具体实现其功能
  • 想获取ColorFactoryBean实例,只需要在获取工厂Bean本身时,在id前面加上&符号即可,例如&colorFactoryBean(Object bean4 = applicationContext.getBean("&colorFactoryBean")
  • package com.meimeixia.bean;
    
    import org.springframework.beans.factory.FactoryBean;
    
    /**
     * 创建一个Spring定义的FactoryBean
     * T(泛型):指定我们要创建什么类型的对象
     * @author liayun
     * 
     */
    public class ColorFactoryBean implements FactoryBean<Color> {
    
        // 返回一个Color对象,这个对象会添加到容器中
        @Override
        public Color getObject() throws Exception {
            // TODO Auto-generated method stub
            System.out.println("ColorFactoryBean...getObject...");
            return new Color();
        }
    
        @Override
        public Class<?> getObjectType() {
            // TODO Auto-generated method stub
            return Color.class; // 返回这个对象的类型
        }
    
        // 是单例吗?
        // 如果返回true,那么代表这个bean是单实例,在容器中只会保存一份;
        // 如果返回false,那么代表这个bean是多实例,每次获取都会创建一个新的bean
        @Override
        public boolean isSingleton() {
            // TODO Auto-generated method stub
            return false;
        }
    
    }
    /***********************************************/
    // 对配置类中的组件进行统一设置
    @Configuration
    public class MainConfig2 {
        
    
        @Bean
        public ColorFactoryBean colorFactoryBean() {
            return new ColorFactoryBean();//bean的类型为Color,名字为colorFactoryBean
        }
        
    }
    
    

8.Bean生命周期以及指定初始化和销毁的方法

@Bean

  • 通过@Bean注解指定初始化和销毁方法,且初始化和销毁方法必须是无参方法、bean的销毁方法是在容器关闭的时候被调用的
    • @Configuration
      public class MainConfigOfLifeCycle {
      
          @Bean(initMethod="init", destroyMethod="destroy")
          public Car car() {
              return new Car();
          }
      
      }
      
      /*****************************************/
      package com.meimeixia.bean;
      
      public class Car {
      
          public Car() {
              System.out.println("car constructor...");
          }
          
          public void init() {
              System.out.println("car ... init...");
          }
          
          public void destroy() {
              System.out.println("car ... destroy...");
          }
          
      }
      
      
      
  • 指定初始化和销毁方法的使用场景:对于数据源的管理(配置数据源且在初始化时,对属性进行赋值,销毁的时候对数据源的连接信息进行关闭和清理)
  • 初始化和销毁方法调用的时机:
    • bean对象的初始化方法调用的时机:对象创建完成,如果对象中存在一些属性,并且这些属性也都赋好值之后,那么就会调用bean的初始化方法。对于单实例bean来说,在Spring容器创建完成后,Spring容器会自动调用bean的初始化方法;对于多实例bean来说,在每次获取bean对象的时候,调用bean的初始化方法
    • bean对象的销毁方法调用的时机:对于单实例bean来说,在容器关闭的时候,会调用bean的销毁方法;对于多实例bean来说,Spring容器不会管理这个bean,也就不会自动调用这个bean的销毁方法了。不过,小伙伴们可以手动调用多实例bean的销毁方法。

9.使用InitializingBean和DisposableBean来管理bean的生命周期

  • InitializingBean接口的调用时机
    • Spring为bean提供了两种初始化的方式,实现InitializingBean接口(也就是要实现该接口中的afterPropertiesSet方法),或者在配置文件或@Bean注解中通过init-method来指定,两种方式可以同时使用
    • 实现InitializingBean接口是直接调用afterPropertiesSet()方法,与通过反射调用init-method指定的方法相比,效率相对来说要高点。但是init-method方式消除了对Spring的依赖
    • 如果调用afterPropertiesSet方法时出错,那么就不会调用init-method指定的方法了
    • afterPropertiesSet方法在属性初始化后执行,同时使用先调用afterPropertiesSet方法,后执行init-method指定的方法
  • DisposableBean接口
    • 实现DisposableBean接口的bean在销毁前,Spring将会调用DisposableBean接口的destroy()方法
    • 多实例bean的生命周期不归Spring容器来管理,这里的DisposableBean接口中的方法是由Spring容器来调用的,所以如果一个多实例bean实现了DisposableBean接口是没有啥意义的,因为相应的方法根本不会被调用
  • package com.meimeixia.bean;
    
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Cat implements InitializingBean, DisposableBean {
        
        public Cat() {
            System.out.println("cat constructor...");
        }
    
        /**
         * 会在容器关闭的时候进行调用
         */
        @Override
        public void destroy() throws Exception {
            // TODO Auto-generated method stub
            System.out.println("cat destroy...");
        }
    
        /**
         * 会在bean创建完成,并且属性都赋好值以后进行调用
         */
        @Override
        public void afterPropertiesSet() throws Exception {
            // TODO Auto-generated method stub
            System.out.println("cat afterPropertiesSet...");
        }
    
    }
    
    /**********************************************************/
    @Test
    public void test02() {
        // 1. 创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完成");
        System.out.println("--------");
        
        // 调用时创建对象
        Object bean = applicationContext.getBean("cat");
        System.out.println("--------");
        
        // 调用时创建对象
        Object bean1 = applicationContext.getBean("cat");
        System.out.println("--------"); 
        
        // 关闭容器
        applicationContext.close();
    }
    
    

10.@PostConstruct注解和@PreDestroy注解

@PostConstruct

  • 是Java中的注解
  • 修饰一个非静态的void()方法。被@PostConstruct注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。被@PostConstruct注解修饰的方法通常在构造函数之后,init()方法之前执行
  • 在Spring框架中的执行顺序:Constructor(构造方法)→@Autowired(依赖注入)→@PostConstruct(注释的方法)→init()方法

@PreDestroy

  • @PreDestroy注解修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy注解修饰的方法会在destroy()方法之后
  • 在spring框架中的执行顺序:调用destroy()方法→@PreDestroy→destroy()方法→bean销毁

11.BeanPostProcessor后置处理器

  • BeanPostProcessor是一个接口
    • postProcessBeforeInitialization()方法:在bean实例化和属性设置之后,自定义初始化方法之前被调用(先于afterPropertiesSet方法执行)
    • postProcessAfterInitialization()方法:会在自定义初始化方法之后被调用
  • 当容器中存在多个BeanPostProcessor的实现类时,会按照它们在容器中注册的顺序执行
  • 作用:
    • 可用于bean对象初始化前后进行逻辑增强。
    • Spring提供了BeanPostProcessor接口的很多实现类
      • AutowiredAnnotationBeanPostProcessor用于@Autowired注解的实现,
      • AnnotationAwareAspectJAutoProxyCreator用于Spring AOP的动态代理:就是在AnnotationAwareAspectJAutoProxyCreator后置处理器的postProcessAfterInitialization方法中,即bean对象初始化完成之后,后置处理器会判断该bean是否注册了切面,若是,则生成代理对象注入到容器中
    • 除此之外,我们还可以自定义BeanPostProcessor接口的实现类
    • package com.meimeixia.bean;
      
      import org.springframework.beans.BeansException;
      import org.springframework.beans.factory.config.BeanPostProcessor;
      import org.springframework.core.Ordered;
      import org.springframework.stereotype.Component;
      
      /**
       * 后置处理器,在初始化前后进行处理工作
       * @author liayun
       *
       */
      @Component // 将后置处理器加入到容器中,这样的话,Spring就能让它工作了
      public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {
      
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              // TODO Auto-generated method stub
              System.out.println("postProcessBeforeInitialization..." + beanName + "=>" + bean);
              return bean;
          }
      
          @Override
          public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              // TODO Auto-generated method stub
              System.out.println("postProcessAfterInitialization..." + beanName + "=>" + bean);
              return bean;
          }
      
          @Override
          public int getOrder() {
              // TODO Auto-generated method stub
              return 3;
          }
      
      }
      
      
  • 带BeanPostProcessor的bean初始化执行流程
    • 伪代码实现
      populateBean(beanName, mbd, instanceWrapper); // 给bean进行属性赋值
      initializeBean(beanName, exposedObject, mbd)
      {
        //1.遍历所有BeanPostProcessor对象,然后依次执行所有BeanPostProcessor对象的postProcessBeforeInitiallization方法,直到for循环结束或者有一个返回null
          applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
          // 2.执行自定义初始化,包括initmethod属性指定的方法、@PostConstruct注解标注的方法;实现InittializingBean接口的方法
          invokeInitMethods(beanName, wrappedBean, mbd); 
          //3.依次执行所有BeanPostProcessor对象的postProcessAfterInitiallization方法
          applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
      }
      
      
  • BeanPostProcessor几个常用实现类在Spring中的应用
    • ApplicationContextAwareProcessor:作用是可以向组件中注入IOC容器(ApplicationContext)
        /**
       * ApplicationContextAwareProcessor这个类的作用是可以帮我们在组件里面注入IOC容器,
       * 怎么注入呢?我们想要IOC容器的话,比如我们这个Dog组件,只需要实现ApplicationContextAware接口就行
       * 
       * @author liayun
       *
       */
      @Component
      public class Dog implements ApplicationContextAware {
          
          private ApplicationContext applicationContext;
      
          public Dog() {
              System.out.println("dog constructor...");
          }
          
          // 在对象创建完成并且属性赋值完成之后调用
          @PostConstruct
          public void init() { 
              System.out.println("dog...@PostConstruct...");
          }
          
          // 在容器销毁(移除)对象之前调用
          @PreDestroy
          public void destory() {
              System.out.println("dog...@PreDestroy...");
          }
      
        /**
         * 传入的applicationContext(ioc容器)就是通过ApplicationContextAwareProcessor这个类帮我们得到的
         * 
         * */
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 在这儿打个断点调试一下
              // TODO Auto-generated method stub
              this.applicationContext = applicationContext;
          }
          
      }
      
      
      /**************************************************/
      //在ApplicationContextAwareProcessor#postProcessBeforeInitialization方法中通过这种方式传入ioc容器
      if (bean instanceof ApplicationContextAware) {
          ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
      }
      
      
      
    • BeanValidationPostProcessor:作用是用来为bean进行校验操作的
      在postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法中的主要逻辑都是调用doValidate()方法对bean进行校验,
      只不过在这两个方法中都会对afterInitialization这个boolean类型的成员变量进行判断,
      若afterInitialization的值为false,则在postProcessBeforeInitialization()方法中调用doValidate()方法对bean进行校验;
      若afterInitialization的值为true,则在postProcessAfterInitialization()方法中调用doValidate()方法对bean进行校验
      
      
    • InitDestroyAnnotationBeanPostProcessor:作用是用来处理@PostConstruct注解和@PreDestroy注解
      postProcessBeforeInitialization{
        //首先会找到bean中有关生命周期的注解,比如@PostConstruct注解等
        LifecycleMetaData=findLifecycleMetadata(bean.getClass());
        //调用metadata的invokeInitMethods()方法,通过反射来调用标注了@PostConstruct注解的方法
        metadata.invokeInitMethods(bean,beanName);
      }
      
    • AutowiredAnnotationBeanPostProcessor类:作用是用于处理标注了@Autowired注解的变量或方法

12.使用@Value注解为bean的属性赋值

@value

  • 可以标注在字段(ElementType.FIELD)、方法(ElementType.METHOD)、参数(ElementType.PARAMETER)以及注解上(ElementType.ANNOTATION_TYPE),而且在程序运行期间生效
  • 属性:value值
  • 使用
    • 注入普通字符串
      @Value("java")
      private String name; // 注入普通字符串
      
      
    • 注入操作系统属性
      @Value("#{systemProperties['os.name']}")
      private String systemPropertiesName; // 注入操作系统属性
      
    • 注入SpEL表达式结果
      @Value("#{ T(java.lang.Math).random() * 100.0 }")
      private double randomNumber; //注入SpEL表达式结果
      
      
    • 注入其他bean中属性的值
      @Value("#{person.name}")
      private String username; // 注入其他bean中属性的值,即注入person对象的name属性中的值
      
      
    • 注入文件资源
      @Value("classpath:/config.properties")
      private Resource resourceFile; // 注入文件资源
      
      
    • 注入URL资源
      @Value("http://www.baidu.com")
      private Resource url; // 注入URL资源
      
  • @Value中#{···}和${···}的区别
    • #{···}:用于执行SpEl表达式,并将内容赋值给属性(@Value(“#{‘Hello World’.concat(‘!’)}”))//调用字符串Hello World的concat方法
    • ⋅ ⋅ ⋅ = = : 主 要 用 于 加 载 外 部 属 性 文 件 中 的 值 ( = = @ V a l u e ( " {···}==:主要用于加载外部属性文件中的值(==@Value(" ====@Value("{author.name:meimeixia}"))//属性文件中没有author.name这个属性,那么便向bean的属性中注入默认值meimeixia
    • #{}与${}可以混用:但是必须==#{}在外面, = = 在 里 面 【 s p r i n g 执 行 ‘ {}==在里面【spring执行` ==spring{}时机要早于#{},当执行外层的${}发现内层的#{}`为空时,会报错】

13.加载配置文件

@PropertySource

  • 标注在类上(ElementType.TYPE)
  • 属性
    • value:string类型数组,表示可以指定多个properties文件
      @PropertySource(value={"classpath:/person.properties", "classpath:/car.properties"})
      

@PropertySources

  • 标注在类上(ElementType.TYPE)
  • 属性
    • value:string类型数组
    • 与@PropertySource配合使用
      @PropertySources(value={
          @PropertySource(value={"classpath:/person.properties"}),
          @PropertySource(value={"classpath:/car.properties"}),
      })
      
      

14.注解自动装配组件

@Autowired、@Qualifier、@Primary

@Autowired注解

  • 标注在构造函数、方法、参数、属性、注解上
  • 属性:required(默认为true)
  • 标注后默认是按照类型去容器中找对应的组件,相当于
    applicationContext.getBean(类名.class);
    

@Qualifier注解

  • 标注在构造函数、方法、参数、属性、注解上
  • @Autowired是根据类型进行自动装配的,需要按名称进行装配,那么就需要配合@Qualifier注解来使用了

@Primary注解

  • 使用到@Autowired这个注解,它默认是根据类型Type来自动注入的。但有些特殊情况,对同一个接口而言,可能会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类

@Resource和@Inject

@Resource

  • 该注解默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,那么默认取字段名将其作为组件的名称在IOC容器中进行查找

@Inject

  • 该注解默认是根据参数名去寻找bean注入,能识别@Primary注解优先注入,@Inject注解还可以增加@Named注解指定要注入的bean

15.使用自定义组件时引用/注入Spring底层的组件

基本使用:Spring提供了很多xxAware接口,只要实现不同的Aware接口就能引入Spring底层的相关组件



/**
 * 以Red类为例来讲解ApplicationContextAware接口、BeanNameAware接口以及EmbeddedValueResolverAware接口
 * @author liayun
 *
 */
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
	
	private ApplicationContext applicationContext;

  /**
   * 实现ApplicationContextAware接口重写的方法,能够引入ioc容器
   * 
   **/
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("传入的IOC:" + applicationContext);
		this.applicationContext = applicationContext;
	}

	/**
	 * 参数name:IOC容器创建当前对象时,为这个对象起的名字
	 * 
	 * 实现BeanNameAware接口重写的方法,能够得到bean的名称
	 * 
	 */
	@Override
	public void setBeanName(String name) {
		System.out.println("当前bean的名字:" + name);
	}
	
	/**
	 * 参数resolver:IOC容器启动时会自动地将这个String值的解析器传递过来给我们
	 * 
	 * 实现EmbeddedValueResolverAware接口重写的方法,能够解析由${}和#{}包裹的值
	 */
	@Override
	public void setEmbeddedValueResolver(StringValueResolver resolver) {
		String resolveStringValue = resolver.resolveStringValue("你好,${os.name},我的年龄是#{20*18}");
		System.out.println("解析的字符串:" + resolveStringValue);
	}

}

XxxAware原理

  • XxxAware接口的底层原理是由XxxAwareProcessor实现类实现的,也就是说每一个XxxAware接口都有它自己对应的XxxAwareProcessor实现类
  • 实现原理源码可参考ApplicationContextAware接口

AOP类型

16.完整AOP注解使用

  • 步骤:
    • 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)
    • 在切面类上的每个通知方法上标注通知注解,要写好切入点表达式
    • 开启基于注解的AOP模式(@EnableAspectJAutoProxy)
  • 代码示范
  • /**
     * ①切面类与业务逻辑组件都加入到容器中
     * ③开启基于注解的AOP模式
     *        
     * @author liayun
     *
     */
    @EnableAspectJAutoProxy
    @Configuration
    public class MainConfigOfAOP {
    	
    	// 将业务逻辑类(目标方法所在类)加入到容器中
    	@Bean
    	public MathCalculator calculator() {
    		return new MathCalculator();
    	}
    	
    	// 将切面类加入到容器中
    	@Bean
    	public LogAspects logAspects() {
    		return new LogAspects();
    	}
    
    }
    
    /************************************************/
    
    
    /**
     * ②定义为切面类,并标注通知注解、写好切入点表达式
     * 
     * @Aspect:告诉Spring当前类是一个切面类,而不是一些其他普通的类
     * @author liayun
     *
     */
    @Aspect
    public class LogAspects {
    	
    	// 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式
    	@Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))")
    	public void pointCut() {}
    	
    	// @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
    	// @Before("public int com.meimeixia.aop.MathCalculator.*(..)")
    	@Before("pointCut()")
    	public void logStart() {
    		System.out.println("除法运行......@Before,参数列表是:{}");
    	}
    	
    	// 在目标方法(即div方法)结束时被调用
    	// @After("pointCut()")
    	@After("com.meimeixia.aop.LogAspects.pointCut()")
    	public void logEnd() {
    		System.out.println("除法结束......@After");
    	}
    	
    	// 在目标方法(即div方法)正常返回了,有返回值,被调用
    	@AfterReturning("pointCut()")
    	public void logReturn() {
    		System.out.println("除法正常返回......@AfterReturning,运行结果是:{}");
    	}
    	
    	// 在目标方法(即div方法)出现异常,被调用
    	@AfterThrowing("pointCut()")
    	public void logException() {
    		System.out.println("除法出现异常......异常信息:{}");
    	}
    
    }
    
    
    
    

17.使注解版的AOP功能起作用,@EnableAspectJAutoProxy注解

@EnableAspectJAutoProxy

  • 使用了@Import注解给容器中引入了AspectJAutoProxyRegister类

    • 该类实现了ImportBeanDefinitionRegistrar接口,从而将自定义的组件添加到IOC容器中,也即AnnotationAwareAspectJAutoProxyCreator【注解装配模式的AspectJ切面自动代理创建器
      类继承关系:
      AnnotationAwareAspectJAutoProxyCreator
          ->AspectJAwareAdvisorAutoProxyCreator(父类)
              ->AbstractAdvisorAutoProxyCreator(父类)
                  ->AbstractAutoProxyCreator(父类)
                      implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware(两个接口)
      
      结论:AnnotationAwareAspectJAutoProxyCreator不仅是一个后置处理器,还是一个BeanFactoryAware接口的实现类
      
    • registerBeanPostProcessors(beanFactory);//注册bean的所有后置处理器,用于拦截bean创建
      ->String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);//拿到IOC容器中所有需要创建的后置处理器
      ->按实现接口类型分离成不同postProcess数组
      ->doCreateBean()//bean实例的创建(AnnotationAwareAspectJAutoProxyCreator)【包括:创建bean的实例->populateBean()->initializeBean()】
      ->判断我们这个bean对象是不是Aware接口,并处理Aware接口的方法回调
      ->拿到所有的后置处理器,再调用后置处理器的postProcessBeforeInitialization()方法
      ->初始化方法执行
      ->拿到所有的后置处理器,然后再调用后置处理器的postProcessAfterInitialization()方法
      ->internalPostProcessors;//拿到所有的BeanPostProcessor,然后调用beanFactory的addBeanPostProcessor()方法将BeanPostProcessor注册到BeanFactory中
      ->initBeanFactory()//初始化beanfactory,生成aspectJAdvisorFactory和aspectJAdvisorsBuilder

      finishBeanFactoryInitialization(beanFactory);//完成BeanFactory的初始化工作
      postProcessBeforeInstantiation()【区别于postProcessbeforeInitialization,继承接口为InstantiationAwareBeanPostProcessor】
      ->preInstantiateSingletons()//遍历获取容器中所有的bean,并依次创建对象(先从缓存中获取当前bean,如果能获取到,说明当前bean之前是被创建过的,那么就直接使用,否则的话再创建)【单例模式】
      ->resolveBeforeInstantiation()//
      ->applyBeanPostProcessorsBeforeInstantiation()//拿到所有的后置处理器,如果后置处理器是InstantiationAwareBeanPostProcessor这种类型的,那么就执行该后置处理器的postProcessBeforeInstantiation()方法
      ->doCreateBean()//创建新bean

  • AOP原理总结

    • 通过@EnableAspectJAutoProxy注解向IOC容器中注册一个AnnotationAwareAspectJAutoProxyCreator组件(后置处理器)
      • registerBeanPostProcessors(beanFactory); // 注册后置处理器,在这一步会创建AnnotationAwareAspectJAutoProxyCreator对象
        
      • finishBeanFactoryInitialization(beanFactory); // 完成BeanFactory的初始化工作。所谓的完成BeanFactory的初始化工作,其实就是来创建剩下的单实例bean的(要增强的类以及切面类)。
        
        • 创建业务逻辑组件(要增强的类)和切面组件(切面类)
        • 判断逻辑组件是否需要增强,如需要,则会把切面里面的通知方法包装成增强器,然后再为业务逻辑组件创建一个代理对象,代理方式根据是否有实现接口决定采用jdk还是cglib;一旦这个代理对象创建出来了,那么它里面就会有所有的增强器
      • 代理对象来执行目标方法
        • 使用CglibAopProxy类的intercept()方法来拦截目标方法的执行
          • 将每一个通知方法又被包装为了方法拦截器,即MethodInterceptor
          • 利用拦截器的链式机制,依次进入每一个拦截器中进行执行
          • 最终执行效果 正常情况:@Before= => 目标方法= =>@AfterReturning= =>@After

18.声明式事务

@Transactional

使用步骤:

  • 配置数据源以及JdbcTemplate
    • /**
       *
       * @author liayun
       *
       */
      @Configuration
      @EnableTransactionManagement // 它是来开启基于注解的事务管理功能的
      public class TxConfig {
      
      	// 注册c3p0数据源
      	@Bean
      	public DataSource dataSource() throws Exception {
      		ComboPooledDataSource dataSource = new ComboPooledDataSource();
      		dataSource.setUser("root");
      		dataSource.setPassword("liayun");
      		dataSource.setDriverClass("com.mysql.jdbc.Driver");
      		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
      		return dataSource;
      	}
      	
      	//使用jdbctemplate
      	@Bean
        public JdbcTemplate jdbcTemplate(DataSource dataSource) throws Exception {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            return jdbcTemplate;
        }
      	
      }
      
      
  • 使用事务注解执行事务
    • @Transactional
      public void insertUser() {
      	userDao.insert();
      	// otherDao.other(); // 该方法中的业务逻辑势必不会像现在这么简单,肯定还会调用其他dao的方法
      	System.out.println("插入完成...");
      	
      	int i = 10 / 0;
      }
      
      
  • 配置事务管理器来控制事务(向TxConfig配置类中添加一个如下方法)
    • // 注册事务管理器在容器中
      @Bean
      public PlatformTransactionManager platformTransactionManager() throws Exception {
      	return new DataSourceTransactionManager(dataSource());
      }
      
      

声明式事务的原理

  • 利用@EnableTransactionManagement注解通过@Import注解给容器导入AutoProxyRegistrar和ProxyTransactionManagementConfiguration这两个组件

  • 导入的第一个组件(即AutoProxyRegistrar)向容器中注入了一个自动代理创建器,即InfrastructureAdvisorAutoProxyCreator【后置处理器】;利用后置处理器机制在对象创建以后进行包装,然后返回一个代理对象,并且该代理对象里面会存有所有的增强器。最后,代理对象执行目标方法,在此过程中会利用拦截器的链式机制,依次进入每一个拦截器中进行执行。

  • ProxyTransactionManagementConfiguration是一个配置类,向容器中注入各种组件

    • 注册事务增强器(BeanFactoryTransactionAttributeSourceAdvisor)

      封装事务属性源、事务拦截器

    • 事务属性源(TransactionAttributeSource)

      解析方法/类上事务注解,包括它里面的每一个属性,例如rollbackFor、noRollbackFor、···

      不但会将事务属性源设置进去,而且还会将事务管理器(txManager)设置进去。也就是说,事务拦截器里面不仅保存了事务属性信息,还保存了事务管理器

    • 事务拦截器(TransactionInterceptor)

      实质上还是一个MethodInterceptor(方法拦截器)

      ①获取事务相关的一些属性信息

      ②从容器中按照类型来获取一个PlatformTransactionManager(从容器中注入)

      ③创建一个事务,并执行目标方法

      ④正常提交返回(commitTransactionAfterReturning):先获取到事务管理器,然后再利用事务管理器提交事务

      ⑤异常返回(completeTransactionAfterThrowing):是先获取到事务管理器,然后再利用事务管理器回滚这次操作

  • 总结:使用AutoProxyRegistrar向Spring容器里面注册一个后置处理器,这个后置处理器会负责给我们包装代理对象。然后,使用ProxyTransactionManagementConfiguration(配置类)再向Spring容器里面注册一个事务增强器,此时,需要用到事务拦截器。最后,代理对象执行目标方法,在这一过程中,便会执行到当前Spring容器里面的拦截器链,而且每次在执行目标方法时,如果出现了异常,那么便会利用事务管理器进行回滚事务,如果执行过程中一切正常,那么则会利用事务管理器提交事务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值