Spring笔记(1) - 组件注册及部分源码解析

  1. @Bean:类注入容器

    1. xml方式:
      <bean id="person" class="com.hrh.bean.Person">
          <property name="name" value="张三"></property>
          <property name="age" value="20"></property>
      </bean>
      public static void main(String[] args) {
          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
          Person person = (Person) context.getBean("person");
          System.out.println(person);
      }
    2. @Bean+@Configuration方式:
      @Configuration//配置类==配置文件
      public class BeanConfig {
          @Bean
          public Person person(){
              return new Person("张三",20);
          }
      }
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
      Person person = (Person)context.getBean("person"); 
  2. @ComponentScan:扫描类注入容器

    1. xml方式:
      <!-- component-scan包扫描,只要标注了@Controller、@Service、@Repository、@Component的类会注入容器中
               use-default-filters=false禁用默认过滤规则,使用自定义过滤规则
          -->
          <context:component-scan base-package="com.hrh" use-default-filters="false"/>
    2. @ComponentScan方式:JDK1.8的ComponentScan是Repeatable的,即可以在类上定义多个@ComponentScan
      @Configuration//配置类==配置文件
      /**
       * value:指定要扫描的包
       * includeFilters:指定扫描时只需要包含哪些组件,需要使用useDefaultFilters = false使规则生效
       * excludeFilters:指定扫描时按照什么规则排除哪些组件
       */
      @ComponentScan(value = "com.hrh",includeFilters = {
              @Filter(type = FilterType.ANNOTATION,
              classes = {Controller.class})
      },useDefaultFilters = false)
      public class BeanConfig {
          @Bean
          public Person person(){
              return new Person("张三",20);
          }
      }
      //====结果====
      org.springframework.context.annotation.internalConfigurationAnnotationProcessor
      org.springframework.context.annotation.internalAutowiredAnnotationProcessor
      org.springframework.context.annotation.internalCommonAnnotationProcessor
      org.springframework.context.event.internalEventListenerProcessor
      org.springframework.context.event.internalEventListenerFactory
      beanConfig
      userController
      person
    3. @ComponentScans:如果不是JDK1.8,可以使用该注解定义多个@ComponentScan
      @ComponentScans({
              @ComponentScan(value = "com.hrh",includeFilters = {
                      @ComponentScan.Filter(type = FilterType.ANNOTATION,
                              classes = {Controller.class})
              },useDefaultFilters = false)
      })
      public class BeanConfig {
          @Bean
          public Person person(){
              return new Person("张三",20);
          }
      }
    4. @Filter:过滤规则
      @Configuration//配置类==配置文件
      /**
       * FilterType.ANNOTATION:根据注解扫描指定的类
       * FilterType.ASSIGNABLE_TYPE:按照给定的类型,如果是接口类型,其实现类和子类注入容器;如果是类,本类和子类注入容器
       * FilterType.ASPECTJ:使用ASPECTJ表达式
       * FilterType.REGEX:使用正则表达式
       * FilterType.CUSTOM:使用自定义规则,自定义实现TypeFilter接口,重写match方法
       */
      @ComponentScan(value = "com.hrh", includeFilters = {
      //        @ComponentScan.Filter(type = FilterType.ANNOTATION,
      //                classes = {IUserService.class}),
      //        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
      //        classes = {IUserService.class}),
              @ComponentScan.Filter(type = FilterType.CUSTOM,classes ={MyTypeFilter.class} )
      }, useDefaultFilters = false)
      public class BeanConfig {
          @Bean
          public Person person() {
              return new Person("张三", 20);
          }
      }
      
      public class MyTypeFilter implements TypeFilter {
          /**
           *
           * @param metadataReader 读取到的当前正在扫描的类的信息
           * @param metadataReaderFactory 可以获取到其他任何类的信息
           * @return ture则将类加入容器
           * @throws IOException
           */
          public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
              //获取当前类注解的信息
              AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
              //获取当前类资源(类路径)
              Resource resource = metadataReader.getResource();
              //获取当前正在扫描的类的类信息
              ClassMetadata classMetadata = metadataReader.getClassMetadata();
              String className = classMetadata.getClassName();
              System.out.println("className:"+className);
              if(className.contains("Controller")){
                  return true;
              }
              return false;
          }
      }
      //====结果====
      className:com.hrh.bean.Person
      className:com.hrh.config.MyTypeFilter
      className:com.hrh.controller.UserController
      className:com.hrh.dao.UserDao
      className:com.hrh.service.IUserService
      className:com.hrh.service.SubUserService
      className:com.hrh.service.UserService
      org.springframework.context.annotation.internalConfigurationAnnotationProcessor
      org.springframework.context.annotation.internalAutowiredAnnotationProcessor
      org.springframework.context.annotation.internalCommonAnnotationProcessor
      org.springframework.context.event.internalEventListenerProcessor
      org.springframework.context.event.internalEventListenerFactory
      beanConfig
      userController
      person
  3. @Scope:作用域

    @Configuration
    @ComponentScan(value = "com.hrh")
    public class BeanConfig {
        /**
         * ConfigurableBeanFactory#SCOPE_PROTOTYPE:prototype
         * ConfigurableBeanFactory#SCOPE_SINGLETON:singleton
         * org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST:request
         * org.springframework.web.context.WebApplicationContext#SCOPE_SESSION:session
         * prototype:多实例(原型),只有在获取的时候才会注入容器,每次获取都会进行创建对象注入不同的实例,每次获取的对象都不同(涉及线程安全时可以用到)
         * singleton:单实例(默认值),ioc容器启动会调用该方法创建对象注入容器,每次获取直接中容器中拿
         * request:同一次请求创建一个实例
         * session:同一个session创建一个实例
         */
    
        @Scope(value = "prototype")
        @Bean
        public Person person() {
            System.out.println("注入容器。。。。。");
            return new Person("张三", 20);
        }
    • @Lazy:懒加载,项目启动时不创建对象注入容器,只在第一次调用时才创建对象并初始化
          @Lazy
          @Bean
          public Person person() {
              System.out.println("注入容器。。。。。");
              return new Person("张三", 20);
          }
    • 在Spring的诸多应用场景中bean都是单例形式,当一个单例bean需要和一个非单例bean组合使用或者一个非单例bean和另一个非单例bean组合使用时,我们通常都是将依赖以属性的方式放到bean中来引用,然后以@Autowired来标记需要注入的属性。但是这种方式在bean的生命周期不同时将会出现很明显的问题,假设单例bean A需要一个非单例bean B(原型),我们在A中注入bean B,每次调用bean A中的方法时都会用到bean B,我们知道Spring Ioc容器只在容器初始化时执行一次,也就是bean A中的依赖bean B只有一次注入的机会,但是实际上bean B我们需要的是每次调用方法时都获取一个新的对象(原型)所以问题明显就是:我们需要bean B是一个原型bean,而事实上bean B的依赖只注入了一次变成了事实上的单例bean。

      @Component
      @Scope("prototype")
      public class PrototypeBean {
          private static final Logger logger= LoggerFactory.getLogger(PrototypeBean.class);
          
          public void say() {
              logger.info("say something...");
          }
      }
      @Component
      public class SingletonBean {
          private static final Logger logger = LoggerFactory.getLogger(SingletonBean.class);
          
          @Autowired
          private PrototypeBean bean;
          
          public void print() {
              logger.info("Bean SingletonBean's HashCode : {}",bean.hashCode());
              bean.say();
          }
      }
      @SpringBootApplication
      public class SampleApplication {
          private static final Logger logger = LoggerFactory.getLogger(SampleApplication.class);
          public static void main(String[] args) {
              SpringApplication.run(SampleApplication.class, args);
          }
      
          @Bean public CommandLineRunner test(final SingletonBean bean) {
              return (args)-> {
                  logger.info("测试单例bean和原型bean的调用");
                  int i =0;
                  while(i<3) {
                      i++;
                      bean.print();
                  }
              };
          }
      }

      1)在bean A中引入ApplicationContext每次调用方法时用上下文的getBean(name,class)方法去重新获取bean B的实例。
      2)使用@Lookup注解。
      这两种解决方案都能解决我们遇到的问题,但是第二种相对而言更简单。

      @Component
      public class SingletonBean {
          private static final Logger logger = LoggerFactory.getLogger(SingletonBean.class);
          
          @Autowired
          private ApplicationContext context;
          
          public void print() {
              PrototypeBean bean = getFromApplicationContext();
              logger.info("Bean SingletonBean's HashCode : {}",bean.hashCode());
              bean.say();
          }
          
          /**
           * 每次都从ApplicatonContext中获取新的bean引用
           * @return PrototypeBean instance
           */
          PrototypeBean getFromApplicationContext() {
              return this.context.getBean("prototypeBean",PrototypeBean.class);
          }
      }
      ===================
      @Component
      public abstract class SingletonBean {
          private static final Logger logger = LoggerFactory.getLogger(SingletonBean.class);
          
          public void print() {
              PrototypeBean bean = methodInject();
              logger.info("Bean SingletonBean's HashCode : {}",bean.hashCode());
              bean.say();
          }
          // 也可以写成 @Lookup("prototypeBean") 来指定需要注入的bean
          @Lookup
          protected abstract PrototypeBean methodInject();
      }
  4. @Conditional:按照条件注入容器,实现Condition的matches()

    @Configuration
    @ComponentScan(value = "com.hrh")
    public class BeanConfig {
        @Conditional(WindowsConfig.class)
        @Bean(value = "Bill")
        public Person person1() {
            System.out.println("创建Windows。。。。。");
            return new Person("Bill", 20);
        }
        @Conditional(LinuxConfig.class)
        @Bean(value = "Linux")
        public Person person2() {
            System.out.println("创建Linux。。。。。");
            return new Person("Linux", 20);
        }
    }
    public class WindowsConfig implements Condition {
        /**
         *
         * @param context 判断条件能使用的上下文
         * @param metadata 注释信息
         * @return
         */
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            //获取ioc使用的beanFactory
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            //获取类加载器
            ClassLoader classLoader = beanFactory.getBeanClassLoader();
            Environment environment = context.getEnvironment();
            String property = environment.getProperty("os.name");
            if("Windows 10".equals(property)){
                return true;//当前系统环境是windows 10,将条件下的bean注入容器
            }
            return false;
        }
    }
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            //获取ioc使用的beanFactory
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            //获取类加载器
            ClassLoader classLoader = beanFactory.getBeanClassLoader();
            Environment environment = context.getEnvironment();
            String property = environment.getProperty("os.name");
            if("Linux".equals(property)){
                return true;
            }
            return false;
        }
  5.  @Import:快速导入组件进容器

    public class Color {
    }
    @Configuration
    @ComponentScan(value = "com.hrh")
    @Import(Color.class)
    //@Import({Color.class,Person.class})导入多个
    public class BeanConfig {}
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for(String beanName:beanDefinitionNames){
            System.out.println(beanName);
        }
    }
    1. ImportSelector:实现该接口,重写selectImports()

      public class MyImportSelector implements ImportSelector {
          /**
           *
           * @param importingClassMetadata 当前标注@Import的类的全部注解信息,比如该类还标注了@Configuration也会被获取到
           * @return 返回的组件全类名加入到容器中
           */
          public String[] selectImports(AnnotationMetadata importingClassMetadata) {
              return new String[]{"com.hrh.bean.Color","com.hrh.bean.Person"};
          }
      }
      
      @Configuration
      @ComponentScan(value = "com.hrh")
      @Import({MyImportSelector.class})
      public class BeanConfig {}
    2. ImportBeanDefinitionRegistrar:实现ImportBeanDefinitionRegistrar,重写registerBeanDefinitions()

      @Configuration
      @Import({Color.class,MyImportBeanDefinitionRegistrar.class})
      public class BeanConfig {}
      public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
          /**
           *
           * @param importingClassMetadata 当前类的注解信息
           * @param registry BeanDefinition注册类,把所有需要添加到容器中的bean,调用BeanDefinitionRegistryregisterBeanDefinition注册进来
           */
          @Override
          public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
              boolean color = registry.containsBeanDefinition("com.hrh.bean.Color");
              //容器中有Color类才将Person类进行注入
              if(color) {
                  RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Person.class);
                  registry.registerBeanDefinition("person",rootBeanDefinition);
              }
      
          }
      } 
  6. FactoryBean

    public class MyFactoryBean implements FactoryBean<Color> {
        //返回一个对象,并且该对象会注入容器
        @Override
        public Color getObject() throws Exception {
    
            return new Color();
        }
    
        @Override
        public Class<?> getObjectType() {
            return Color.class;
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    }
    @Configuration
    public class BeanConfig {
        /**
         * 1.默认获取的是MyFactoryBean调用getObject创建的对象
         * 2.要获取MyFactoryBean本身,需要给id前加&,比如&myBeanFactory
         */
        @Bean
        public MyFactoryBean myBeanFactory(){
            return new MyFactoryBean();
        }
    }
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
    Object bean1=context.getBean("myBeanFactory");
    System.out.println(bean1.getClass());
    Object bean2=context.getBean("&myBeanFactory");
    System.out.println(bean2.getClass());
posted @ 2020-05-26 22:28  码猿手  阅读(298)  评论(0编辑  收藏  举报
Live2D