Spring 注解驱动开发【01-组件注册】

Spring 注解驱动开发【组件注册篇】

1.组件注册 @Configuration&@Bean给容器中注册组件

  • 自定义配置类 MyConfig
package indi.zhihuali.config;

import indi.zhihuali.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//  配置类 <=> 配置文件

@Configuration //声明spring配置类
public class MyConfig {
    
    
    

//    @Bean 后面也可以加一个参数 作为bean的id 下面的方法名则被覆盖
    @Bean("person")
    // 给容器中注册一个bean 返回类型为bean的类型 方法名为bean的id
    public Person person02(){
    
    
        return new Person("zhihuali",20);
    }

}

  • Test类中测试ioc注入情况
//        通过注解(自定义配置类 @Configuration) 对ioc容器进行bean注入
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
//		获取ioc容器中的bean对象并实例化
        Person person = context.getBean(Person.class);
 		System.out.println(person);
//        遍历获取类型为Person的组件在ioc容器中的id是什么
        String[] beanNamesForType = context.getBeanNamesForType(Person.class);
       
        for (String name : beanNamesForType) {
    
    
            System.out.println(name);
        }

2.组件注册 @ComponentScan 自动扫描组件及指定扫描规则

  • 在自定义配置类中定义@ComponentScan(value=扫描的包)
@Configuration   //声明spring配置类
@ComponentScan(value = "indi.zhihuali")
public class MyConfig {
    
    
}

  • 分别生成组件注解( controller层对应@Controller、mapper层对应@Repository、service层对应@Service 及 @Component)

  • 通过测试类对ioc容器中的组件进行扫描测试

@Test
    public void testComponent(){
    
    
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
//        查看ioc容器中具有哪些组件
//        context.getBeanDefinitionNames() 方法返回容器中所有注册了的bean的名字
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
    }

输出结果为:

myConfig 配置类@Configuration本身就是一个@Component
bookController
bookMapper
bookService
person	通过@Bean注册进ioc容器
  • 在@ComponentScan中添加参数 excludeFilters (除掉哪些bean) 或 includeFilters(包含哪些bean)
@Configuration   //声明spring配置类
//  @ComponentScan  value:指定要扫描的包
//  excludeFilters 返回 Filter[] 是数组
//  @ComponentScan.Filter
//  (type = FilterType.ANNOTATION   根据注解进行过滤
//   classes = {Controller.class, Service.class}    具体要过滤的是哪些类 @Controller对应Controller.class
@ComponentScan(value = "indi.zhihuali",excludeFilters = {
    
    
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
    
    Controller.class, Service.class})
})
public class MyConfig {
    
    
}

添加了excludeFilters后的运行结果

myConfig	配置类@Configuration本身就是一个@Component
bookMapper
person	通过@Bean注册进ioc容器
  • 将@ComponentScan内参数修改为includeFilter
@ComponentScan(value = "indi.zhihuali",includeFilters = {
    
    
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
    
    Controller.class, Service.class})
    }
// 想要设置为只包含什么什么的过滤器 首先要将默认的过滤器关掉 
               ,userDefaultFilters = false
               
          
)

组件扫描器可以指定多个 且互补配置

同样可以通过@ComponentScans进行配置 且value为数组 值为@ComponentScan

@ComponentScans(value = {
    
    
        @ComponentScan(value = "indi.zhihuali",includeFilters = {
    
    
                @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
    
    Controller.class, Service.class})
        },useDefaultFilters = false
        )
})

3.组件注册 自定义TypeFilter指定过滤规则

//  @ComponentScan  value:指定要扫描的包
//  excludeFilters 返回 Filter[] 是数组  指定扫描时按什么规则排除哪些组件
//  includeFilters 指定扫描时只需要包含哪些组件
//  (type = FilterType.ANNOTATION   根据注解进行过滤
//          FilterType.ASSIGNABLE_TYPE 根据指定类型进行过滤
//          FilterType.ASPECTJ  根据ASPECTJ表达式
//          FilterType.REGEX    根据正则表达式
//          FilterType.CUSTOM  自定义过滤规则
//	,
//   classes = {Controller.class, Service.class}    具体要过滤的是哪些类 @Controller对应Controller.class
  • 重点是 采用自定义FilterType.CUSTOM进行过滤
  1. 编写自定义过滤类
public class MyTypeFilter implements TypeFilter {
    
    
    /*

    MetadataReader  读取到的当前正在扫描的类的信息
    MetadataReaderFactory   可以获取到其他任何类信息

    */
    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(classMetadata);
        System.out.println(annotationMetadata);
        System.out.println(resource);
        System.out.println("--------------------------------");
//        若类名中包含er则放行 不包含则拦截
        if(className.contains("er")){
    
    
            return true;
        }
        return false;
    }
}
  1. 在配置类中添加自定义typefilter的信息
@ComponentScans(value = {
    
    
        @ComponentScan(value = "indi.zhihuali",includeFilters = {
    
    
                @ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
        },useDefaultFilters = false
        )
})
  1. 编写测试方法对ioc容器中对象进行打印
@Test
    public void testComponent(){
    
    
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
//        查看ioc容器中具有哪些组件
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
    }
}

打印结果为

org.springframework.core.type.classreading.SimpleAnnotationMetadata@d282e0
org.springframework.core.type.classreading.SimpleAnnotationMetadata@d282e0
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\config\MyTypeFilter.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@438a68
org.springframework.core.type.classreading.SimpleAnnotationMetadata@438a68
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\controller\bookController.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1d15f18
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1d15f18
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\mapper\bookMapper.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1695df3
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1695df3
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\pojo\Person.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1ea5ab4
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1ea5ab4
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\service\bookService.class]
--------------------------------
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
myConfig
myTypeFilter
bookController
bookMapper
person
bookService

注:注解扫描指定包下的所有类 如果满足放行条件则进行放行 否则就拦截

4.组件注册 @Scope 设置组件作用域

@Scope可选参数值

 /*
     * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
     * @see ConfigurableBeanFactory#SCOPE_SINGLETON
     * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
     * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
     
     * value 的四种取值 :
     * prototype   多实例
     * singleton   单例 【默认】
     * request     同一次请求 创建一次实例 【web环境】
     * session     同一个session创建一个实例 【web环境】
   
*/

在没有显式配置Scope时 默认单例模式

@Configuration
public class MyConfig2 {
    
    
    @Bean(value = "person")
    public Person person(){
    
    
        System.out.println("给容器添加Person...");	//看何时进行实例化Person对象
        return new Person("wangjingbo",12);
    }
}
@Test
public void testComponent02(){
    
    
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
}

输出:

给容器添加Person...

通过上例可证明 默认情况下 (单例bean时),在创建Ioc容器时就自动生成bean对象 即用即取

给bean配置作用域

@Configuration
public class MyConfig2 {
    
    
    @Bean(value = "person")
    @Scope(value = "prototype")
    public Person person(){
    
    
        return new Person("wangjingbo",12);
    }
}
@Test
public void testComponent02(){
    
    
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
}

打印结果:空

说明多实例情况下 ioc容器启动时并不会去调用构造器创建对象并放在容器中

那么何时调用呢? 其实是在getBean时调用!

 public void testComponent02(){
    
    
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
        System.out.println("ioc容器初始化完成");
        Object person = context.getBean("person");
    }
}
ioc容器初始化完成
给容器添加Person...

若获取多个对象 则调用多次

Object person = context.getBean("person");
Object person1 = context.getBean("person");
ioc容器初始化完成
给容器添加Person...
给容器添加Person...

5.组件注册 @Lazy 懒加载bean 【针对@Scope(“singleton”)】

默认情况下 单例模式下添加bean会在创建Ioc容器时提前自动生成bean对象

通过添加@Lazy 可以达到在创建ioc容器时不再提前自动生成bean对象 而用时生成的效果

@Bean(value = "person")
@Lazy   //  懒加载
    public Person person(){
    
    
        System.out.println("给容器添加Person...");
        return new Person("wangjingbo",12);
    }

6.组建注册 @Conditional 按照条件注册bean

@Conditional({condition数组}):按照一定的条件进行判断 满足条件给容器中注册bean
例如:如果系统是 windows 给容器中注册 bill
   	 如果系统是 linux 给容器中注册 linus

将注解标注在方法上 对单一bean进行condition判断

将注解标注在类上 满足当前条件 类中的任何bean注册才会生效 类中组件统一设置

@Configuration
public class MyConfig2 {
    
    
    /*
    *   @Conditional({condition数组}):按照一定的条件进行判断 满足条件给容器中注册bean
    *   例如:如果系统是 windows 给容器中注册 bill
    *        如果系统是 linux 给容器中注册 linus
    * */
    @Conditional({
    
    LinuxCondition.class})
    @Bean("linus")
    public Person person01(){
    
    
        return new Person("Linus",22);
    }
    @Conditional({
    
    WindowsCondition.class})
    @Bean("bill")
    public Person person02(){
    
    
        return new Person("Bill",27);
    }
}

编写:LinuxCondition.class实现Condition接口

public class LinuxCondition implements Condition {
    
    
    /*
    * ConditionContext 判断条件能使用的上下文环境
    * AnnotatedTypeMetadata 注释信息
    * */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
//        判断是否linux系统:
        
//        1.能获取到ioc使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//        2.获取类加载器
        ClassLoader classLoader = context.getClassLoader();
//        3.获取当前环境信息
        Environment environment = context.getEnvironment();
//        4.获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

//        Condition可以判断的东西有很多:
//        可以通过下面的方法判断容器中是否包含名为person的bean 
//        也可以通过一些方法注册bean等等
        boolean person = registry.containsBeanDefinition("person");
        
//        通过environment.getProperty("os.name")方法可以查看当前操作系统信息
        String property = environment.getProperty("os.name");
        if(property.contains("Linux")){
    
    
            return true;
        }
        return false;
    }
}

WindowsCondition.class 实现Condition接口

public class WindowsCondition implements Condition {
    
    
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        if(property.contains("Windows")){
    
    
            return true;
        }
        return false;
    }
}

默认情况下操作系统为Windows10 所以会满足WindowsCondition的条件 即注入bill

进行测试

 @Test
    public void testComponent03(){
    
    
//          动态获取环境变量的值 操作系统值:windows 10
        ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment();
        String property = environment.getProperty("os.name");
        System.out.println(property);

//        1、获取Person类bean对象的名字并遍历输出
        String[] beanNamesForType = context.getBeanNamesForType(Person.class);
        for (String name : beanNamesForType) {
    
    
            System.out.println(name);
        }
//        2、获取Person类bean对象并输出
        Map<String, Person> persons = context.getBeansOfType(Person.class);
        System.out.println(persons);
    }

输出:

Windows 10
bill
{bill=Person(name=Bill, age=27)}

可以通过editConfigurations

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7V4vEeHx-1600441483178)(C:\Users\zhihua.Li\AppData\Roaming\Typora\typora-user-images\image-20200916233646993.png)]

给虚拟机设置参数 达到修改os.name的效果从而使LinuxCondition生效而注入linus bean对象 测试同上

结果:

linux
{}

7.组件注册 @Import 给容器中快速导入一个组件

给容器中添加组件的几种方法:
1.针对于自己写的类:包扫描+组件定义(@Component @Repository @Service @Controller)
2.针对于不是自己写的类 如第三方导入时:@Configuration & @Bean
3.针对于不是自己写的类 如第三方导入时 若通过Bean导入 还需要给实体类添加无参构造器 较为复杂
  同样可以快速为容器中导入一个组件:@Import({})
  1.@Import(要导入的组件) 容器中就会自动注册这个组件 id默认为全类名	见7.3.1
  2.采用ImportSelector:返回需要导入的组件的全类名数组	见7.3.2
  3.采用ImportBeanDefinitionRegistrar:手动注册bean到ioc容器中
4.使用Spring提供的FactoryBean(工厂bean)注册组件:
  1.默认获取到的是工厂bean调用getObject创建的对象	
  	即通过context.getBean("colorFatoryBean") 获取到的是Color类的对象
  2.要获取工厂bean本身 需要给id前加一个& 即context.getBean("&colorFatoryBean")
	

7.3.1 在配置类上方添加@Import

@Configuration
@Import({
    
    Color.class})
public class MyConfig2 {
    
    
    ....
}

7.3.2 自定义ImportSelector类

  • 编写两个pojo实体类
public class Red{
    
    }
public class Yellow{
    
    }
  • 编写MyImportSelector类实现ImportSelector接口
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
    
    
    
//    返回值 就是导入到容器中的组件全类名 会自动添加到ioc容器中 不可以为null 但可以是空数组
//    AnnotationMetadata:当前标注@Import注解的类的全部注解信息
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    
        
//    可以通过importingClassMetadata.getXXX() 可以通过importingClassMetadata获取多个属性
        
//		将 Red 和 Yellow 添加到ioc容器中(要写全类名)
        return new String[]{
    
    "indi.zhihuali.pojo.Red","indi.zhihuali.pojo.Yellow"};
    }
}
  • 利用@Import添加MyImportSelector类
@Configuration
@Import({
    
    Color.class, MyImportSelector.class})
public class MyConfig2 {
    
    }

**Tips:**全类名的快速获取:

可以右键单击要复制全类名的类->copy->copy reference后 输入双引号并在其中ctrl+v 即可快速复制全类名

测试获取ioc容器中的所有bean 结果为:

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
myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill

可以看到Red和Yellow都被注册进去了

7.3.3 自定义ImportBeanDefinitionRegistrar类实现ImportBeanDefinitionRegistrar接口

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
    
    
    /*
    *
    * AnnotationMetadata:当前类的注解信息 可以获取类的若干信息
    * BeanDefinitionRegistry:BeanDefinition注册类 将所有需要添加的bean
    * 通过调用
    *  registry.registerBeanDefinition() 手工注册到ioc中
    *
    * */
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
                                         BeanNameGenerator importBeanNameGenerator) {
    
    

//		判断容器中是否有名为Red和Yellow的bean 若有则将rainbow也注册进ioc中
        boolean red = registry.containsBeanDefinition("indi.zhihuali.pojo.Red");
        boolean yellow = registry.containsBeanDefinition("indi.zhihuali.pojo.Yellow");
        if(red && yellow){
    
    
//            指定bean的定义信息 (类型 Scope等)
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rainbow.class);

//            注册一个bean 指定bean名
//            registerBeanDefinitions(importingClassMetadata, registry);
//			  importingClassMetadata bean名字 
//			  registry 指定bean的定义信息 (类型 Scope等)       
   
            registry.registerBeanDefinition("rainbow", rootBeanDefinition);
        }
    }
}

测试所得

myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill
rainbow

7.4 自定义FactoryBean类

  • 自定义ColorFactoryBean实现FactoryBean接口
package indi.zhihuali.pojo;

import org.springframework.beans.factory.FactoryBean;

/**
 * @author zhihua.li
 * @date 2020/9/18 - 8:06
 **/

public class ColorFactoryBean implements FactoryBean<Color> {
    
    
//    返回一个Color对象 这个对象会添加到容器中
    public Color getObject() throws Exception {
    
    
//	  在对象被创建时打印标志语句
        System.out.println("get it");
        return new Color();
    }

//    返回对象的类型
    public Class<?> getObjectType() {
    
    
        return Color.class;
    }

//    设置对象是否单例:
//    true:单例 在容器中只保存一份
//    false:多例  每次获取都会创建一个新的


    public boolean isSingleton() {
    
    
        return true;
    }
}
  • 在配置类中注入定义的类
@Bean
    public ColorFactoryBean colorFactoryBean(){
    
    
        return new ColorFactoryBean();
    }
  • 测试ioc容器中bean情况
@Test
    public void testComponent04() throws Exception {
    
    
//		封装了获取bean的方法
        getAllBeans(context);
    }
  • 测试结果
myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill
colorFactoryBean
rainbow
  • 可以发现ioc容器中存在一个名为colorFactoryBean的bean 可定义FactoryBean就是为了注入Color类的bean

    对此进行测试 获取名为ColorFactoryBean的bean的对象并通过反射获取其运行时类

    		Object bean = context.getBean("colorFactoryBean");
            System.out.println("不明类型的bean为:"+bean.getClass());
    

    输出结果为:

    不明类型的bean为:class indi.zhihuali.pojo.Color
    

    可见 虽然其名字为colorFactoryBean 但实际上调用getBean方法时获取到的还是Color对象

  • 是否为单例?

		Object bean1 = context.getBean("colorFactoryBean");
		Object bean2 = context.getBean("colorFactoryBean");	
		System.out.println(bean1 == bean2);
true
  • 如果获取到colorFactoryBean类本身对象呢?

    通过给getBean参数前加一个&即可获得其本身bean

    public interface BeanFactory {
          
          
    /*   * Used to dereference a {@link FactoryBean} instance and distinguish it from
    	 * beans <i>created</i> by the FactoryBean. For example, if the bean named
    	 * {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
    	 * will return the factory, not the instance returned by the factory.
    	 */
        	String FACTORY_BEAN_PREFIX = "&";
    
		Object bean = context.getBean("colorFactoryBean");
        System.out.println("不明类型的bean为:"+bean.getClass());
        Object bean2 = context.getBean("&colorFactoryBean");
        System.out.println("添加了&后获取到的bean为:"+bean2.getClass());

​ 输出结果为:

不明类型的bean为:class indi.zhihuali.pojo.Color
添加了&后获取到的bean为:class indi.zhihuali.pojo.ColorFactoryBean

猜你喜欢

转载自blog.csdn.net/weixin_45729934/article/details/108674498