Spring注解-组件注册(一)

Bean

  • 我们之前使用xml时, 需要使用<bean>标签
  1. 配置
        <bean class="test.bean.Person" id="person">
            <property name="age" value="18"></property>
            <property name="name" value="zhangsan"></property>
        </bean>
  2. 使用
            ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("beans.xml");
    
            Person bean = cac.getBean("person", Person.class);
            System.out.println(bean);
    
            cac.close();
  • 使用注解配置
  1. 需要我们手写配置类, 就相当于以前的配置文件.
    • 配置类上加入@Configuration注解, 告诉Spring这是一个配置类.
    • 在里面使用@Bean注解: 给容器注册一个额Bean, 类型为返回值类型, id默认是方法名.
      • 其中, value属性可以修改id
        @Configuration
        public class MainConfig {
        
            // 给容器中注册一个Bean; 类型为返回值的类型, id默认是方法名
            @Bean(value = "person01")
            public Person person() {
        
                return new Person("lisi", 20);
            }
        }
  2. 使用
    • 使用AnnotationConfigApplicationContext, 包含配置类类型.
      public class MainTest {
      
          public static void main(String[] args) {
      
              AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
              Person bean2 = ac.getBean(Person.class);
              Person bean3 = ac.getBean(Person.class);
              System.out.println(bean2 == bean3);
      
              ac.close();
          }
      }
  3. @Scope注解
    • @Bean默认是单实例的, 我们可以使用@Scope注解来调整作用域
    • scope
      • prototype: 多实例 -> IOC容器启动不会调用方法创建对象, 而是每次获取的时候调用方法创建对象.
      • singleton: 单实例(默认值) -> IOC容器启动就会调用方法创建对象放到容器中. 以后获取直接从从容器中拿.
      • request: 同一次请求创建一个实例
      • sesson: 同一个session创建一个实例(后两个都web环境)
  4. 懒加载 -> 只针对于单实例
    • 单实例原本是在容器启动的时候就创建对象的.
    • 懒加载: 容器启动后, 先不创建对象, 第一次使用(获取)Bean的时候创建对象, 并初始化.
    • @Lazy注解
      @Configuration 
      @ComponentScan(value = "test") 
      public class MainConfig {
      
          // 给容器中注册一个Bean; 类型为返回值的类型, id默认是方法名
          @Bean(value = "person01")
          @Lazy
          @Scope("singleton")
          public Person person() {
      
              return new Person("lisi", 20);
          }
      }

包扫描

  • xml方式
  1. 使用<context:component-scan>
        <!-- 包扫描: 只要标注了@Component, @Service, @Repository, @Controller, 就会被自动扫描加进容器 -->
        <context:component-scan base-package="test"></context:component-scan>
  • 注解方式
  1. 使用@ComponentScan注解, 这是一个可重复注解, 也就是我们可以在一个类上写多次实现多种配置规则
    • value属性填写要扫描的包.
    • excludeFilters = Filter[]: 指定扫描的时候按照什么规则排除哪些组件
    • includeFilters = Filter[]: 指定扫描的时候包含哪些组件
    • useDefaultFilters属性: 是否使用默认配置规则, 默认值为true, 使用includeFilters时需配置成false.
      @ComponentScan(value = "test", excludeFilters = {
          @Filter(type=FilterType.ANNOTATION, classes = {Controller.class, Service.class})
      })
      @ComponentScan(value = "test", includeFilters = {
              @Filter(type=FilterType.ANNOTATION, classes = {Repository.class})
      })
  2. 测试
    • AnnotationConfigApplicationContext有一个getBeanDefinitionNames()获取加载进Spring的组件.
  3. @Filter注解
    • FilterType.ANNOTATION: 按照注解
      • classes属性中填组件的类型.
    • FilterType.ASSIGNABLE_TYPE: 按照给定的类型
      • classes中填写具体类的类型
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes= {BookService.class})
    • FilterType.ASPECTJ: 使用ASPECTJ表达式
    • FilterType.REGEX: 使用正则表达式
    • FilterType.CUSTOM: 使用自定义规则
      • 自定义TypeFilter接口的实现类
        public class MyTypeFilter implements TypeFilter {
        
            /**
             * metadataReader: 读取到当前正在扫描的类的信息
             * metadataReaderFactory: 可以获取到其他任何类信息的.
             * 
             * 返回false是一个也不匹配.
             */
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                    throws IOException {
                
                //获取当前类注解的信息
                AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
                //获取当前正在扫描的类的类信息
                ClassMetadata classMetadata = metadataReader.getClassMetadata();
                String className = classMetadata.getClassName();
                System.out.println(className);
                //获取当前类资源(类的路径等信息)
                Resource resource = metadataReader.getResource();
                
                //类名中有"er"就会被扫描进容器.
                if(className.contains("er")) {
                    return true;
                }
                
                return false;
            }
        
        }
      • 使用
        @Filter(type=FilterType.CUSTOM, classes = {MyTypeFilter.class})

@Conditional注解 

  1. 作用: 按照一定条件进行判断, 满足条件才给容器中注册bean.
  2. @Conditional(): 中填写Condition数组.
    • Condition是个接口, 所以我们要写实现类放入@Conditional中.
    • MaleCondition
      public class MaleCondition implements Condition {
      
          /**
           * ConditionContext: 判断条件能使用的上下文(环境)
           * AnnotatedTypeMetadata: 注释信息
           * 返回true则允许注册.
           */
          @Override
          public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
              
              //是否为男
              //1.能获取到ioc使用的beanfactory
              ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
              
              //2.获取类加载器
              ClassLoader classLoader = context.getClassLoader();
              
              //3.获取当前环境信息
              Environment environment = context.getEnvironment();
              
              //获取到bean定义的注册类
              BeanDefinitionRegistry registry = context.getRegistry();
              
              String property = environment.getProperty("os.name");
              //当前系统是否为Windows
              if(property.contains("Windows")) {
                  return true;
              }
              return false;
          }
      }
    • FemaleCondition
      public class FemaleCondition implements Condition {
      
          @Override
          public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      
              Environment environment = context.getEnvironment();
              
              String property = environment.getProperty("os.name");
              if(property.contains("Linux")) {
                  return true;
              }
              return false;
          }
      }
    • 使用
          @Conditional({ MaleCondition.class })
          @Bean("ming")
          public Person person001() {
      
              return new Person("xiaoming", 16);
          }
      
          @Conditional({ FemaleCondition.class })
          @Bean("hong")
          public Person person002() {
              return new Person("xiaohong", 15);
          }
  3. 不仅仅能放在方法上, 也能放在类上
    • 只有满足了当前条件, 这个类中配置的所有bean注册才能生效.

 @Import注解

  • 给容器中注册组件
  1. 包扫描+组件注解标注(@Controller等...)
    • 但有局限, 只能注册我们自己写的类.
  2. @Bean
    • 导入的第三方包里面的组件
  3. @Import
    • 快速给容器中导入一个组件
    • @Import(要导入到容器中的组件), 容器中就会自动注册这个组件, id默认是全类名
      @Configuration
      @ComponentScan(value = "test")
      @Import({Color.class, Red.class}) //导入组件, id默认是组件的全类名
      public class MainConfig {
      
          //...
      }
  4. ImportSelector
    • 自定义逻辑, 返回要导入的组件, 这是一个接口, 我们要在实现类中操作.
      //自定义逻辑, 返回需要导入的组件
      public class MyImportSelector implements ImportSelector {
      
          /**
           * 返回值就是导入到容器中的组件的全类名 
           * AnnotationMetadata: 当前标注@Import的注解类的所有注解信息.
           */
          @Override
          public String[] selectImports(AnnotationMetadata importingClassMetadata) {
              
              // 方法不能返回null值, 可以返回一个空数组
              return new String[] {"test.bean.Blue", "test.bean.Yellow"};
          }
      
      }
    • 当然, 我们需要把实现类写入@Import中.
      @Configuration
      @ComponentScan(value = "test")
      @Import({Color.class, Red.class, MyImportSelector.class}) //导入组件, id默认是组件的全类名
      public class MainConfig {
      
          //...
      }
  5. ImportBeanDefinitionRegistrar
    • 手工注册bean到容器中, 需要实现ImportBeanDefinitionRegistrar接口.
      public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
      
          /**
           * BeanDefinitionRegistry: BeanDefinition注册类, 把所有需要添加到容器中的bean, 
           *   调用BeanDefinitionRegistry.registerBeanDefinition手工注册进来
           */
          @Override
          public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
              
              //是否包含"red"这个组件
              boolean definition = registry.containsBeanDefinition("red");
              boolean definition2 = registry.containsBeanDefinition("blue");
              if(definition && definition2) {
                  //指定Bean信息: (Bean的类型, Scope等)
                  RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
                  //指定bean名
                  registry.registerBeanDefinition("rainBow", beanDefinition);
              }
          }
      
      }
    • 当然, 我们需要把实现类写入@Import中.
      @Configuration
      @ComponentScan(value = "test")
      @Import({MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) //导入组件, id默认是组件的全类名
      public class MainConfig {
      
          //...
      }

FactoryBean(工厂Bean)

  1. 实现FactoryBean接口, 让工厂帮我们创建对象.
    //创建一个Spring定义的FactoryBean
    public class ColorFactoryBean implements FactoryBean<Color> {
    
        // 返回一个Color对象, 这个对象会添加到容器中
        @Override
        public Color getObject() throws Exception {
            // TODO Auto-generated method stub
            return new Color();
        }
    
        @Override
        public Class<?> getObjectType() {
            // TODO Auto-generated method stub
            return Color.class;
        }
    
        // 是否单例
        @Override
        public boolean isSingleton() {
    
            return false;
        }
    
    }
  2. 装配
        @Bean
        public ColorFactoryBean colorFactoryBean() {
            return new ColorFactoryBean();
        }
  3. 测试
    • 虽说装配的是ColorFactoryBean, 但我们实际获取的却是其泛型中的实体对象.
          @Test
          public void test() {
              AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
              Object bean = ac.getBean("colorFactoryBean");
              System.out.println(bean);
              Object bean2 = ac.getBean("&colorFactoryBean");
              System.out.println(bean2);
          }
    • 但我们在id前加上&符, 就能获取工厂Bean本身.
posted @ 2020-06-09 11:10  yellowstreak  阅读(191)  评论(0编辑  收藏  举报