美文网首页
Spring注解驱动开发系列-注册组件的方式

Spring注解驱动开发系列-注册组件的方式

作者: lclandld | 来源:发表于2020-04-07 14:11 被阅读0次

    对于一个以前从事Android的开发者来说,现在转到java后台开发,就直接上手SpringBoot,虽然平时的工作中大多数是进行的增删查改,但是我也想自己提升,就看了SpringBoot的源码,但是在看源码的过程中,发现很多的原理还是不懂,于是现在回头从基础的Spring注解开始学习起,以下是我利用闲暇时间通过看视频所学习到的,在这儿只是为了记录一下,以后想回顾的时候,好随时查找,如果有一些错误的认知,烦请指教,谢谢!

    Spring的重要特性之一是IOC容器和DI,Spring是把所有的组件都统一放到IOC容器中,然后组件之间的关系通过容器来进行自动装配,也就是DI。所以下面整体要记录的主要是用纯注解的方式来(顺便回顾下以往)完成容器的组件注册、管理以及依赖注入功能

    1、以前用bean.xml配置文件的方式

    • 在resources下新建一个bean.xml,用<bean>标签注册组件
      Person类就不写了,就是一个普通的POJO类
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
        <bean id="person" class="com.atguigu.bean.Person" >
            <property name="age" value="20"></property>
            <property name="name" value="zhangsan"></property>
        </bean>
    </beans>
    
    
    • 如何知道这个组件是否已经被加入到容器中
    public class MainTest {
        @SuppressWarnings("resource")
        public static void main(String[] args) {
            //获取容器
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
            //通过组件名在容器中获取组件
            Person bean = (Person) applicationContext.getBean("person");
            System.out.println(bean);
        }
    }
    
    • 结果


      image.png

    2、用注解@Configuration和@Bean的方式

    • @Configuration是3.0发布的,用于定义配置类,可替换bean.xml配置文件
      @Bean就相当于xml中的<bean>标签
    //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id,也可以自己在@Bean中自己写@Bean("person")
    @Bean("person")
    public Person person(){
        return new Person("lisi", 20);
    }
    
    • 如何知道这个组件是否已经被加入到容器中
    public class MainTest {
        @SuppressWarnings("resource")
        public static void main(String[] args) {
            //获取容器
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
            //通过组件类型在容器中获取组件
            Person bean = applicationContext.getBean(Person.class);
            System.out.println(bean);
        }
    }
    
    • 结果


      image.png

    3、包扫描@ComponentScan+标注(@Controller、@Service、@Repository,@Component)的方式

    • 对于以前bean.xml方式
      1)、在xml中写上这个
      <context:component-scan base-package="com.atguigu" use-default-filters="false"></context:component-scan>
      2)、只要在POJO类上标注了@Controller、@Service、@Repository,@Component就可以注册到容器中
      3)、访问容器用ClassPathXmlApplicationContext就行
    • 在配置类上加入注解@ComponentScan(value="com.atguigu")
      1)、只要在value="com.atguigu"这个包路径下的所有含有标注的POJO都会被加载
      2)、访问容器用AnnotationConfigApplicationContext就行

    4、@Import[快速给容器中导入一个组件]

    4.1、通过下面注解的定义可以看到,此注解只能放到类上,value是一个类数组,但是value是可以有多种方式的,所有下面将对这三种 {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}方式分别进行一个记录描述
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Import {
    
        /**
         * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
         * or regular component classes to import.
         */
        Class<?>[] value();
    
    }
    
    4.2、给容器导入组件
    • 1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名。
    @Import({Color.class,Red.class})
    public class MainConfig2 {
    }
    
    //把所有注册进去容器的组件打印一遍
    @Test
    public void testImport(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }
    
    Import导入的两个,配置类mainConfig2也是组件,是因为可以看到@Configuration里面是有@Component标注的,其余的是系统自动注册的一些组件
    • 2)、ImportSelector:返回需要导入的组件的全类名数组;在SpringBoot底层使用很多
    //可以看到ImportSelector是一个接口,所有要自定义一个类,实现此接口
    public interface ImportSelector {
    
        /**
         * Select and return the names of which class(es) should be imported based on
         * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
         */
        String[] selectImports(AnnotationMetadata importingClassMetadata);
    
    }
    
    
    //自定义逻辑返回需要导入的组件
    public class MyImportSelector implements ImportSelector {
    
        //返回值,就是到导入到容器中的组件全类名
        //AnnotationMetadata:当前标注@Import注解的类的所有注解信息
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // TODO Auto-generated method stub
            //importingClassMetadata
            //方法不要返回null值
            return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Yellow"};
        }
    }
    
    
    //具体的使用方式
    @Import({Color.class,Red.class,MyImportSelector.class})
    
    结果
    • 3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
    //可以看到ImportBeanDefinitionRegistrar 是一个接口,所有要自定义一个类,实现此接口
    public interface ImportBeanDefinitionRegistrar {
    
        /**
         * Register bean definitions as necessary based on the given annotation metadata of
         * the importing {@code @Configuration} class.
         * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
         * registered here, due to lifecycle constraints related to {@code @Configuration}
         * class processing.
         * @param importingClassMetadata annotation metadata of the importing class
         * @param registry current bean definition registry
         */
        public void registerBeanDefinitions(
                AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
    
    }
    
    
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
        /**
         * AnnotationMetadata:当前类的注解信息
         * BeanDefinitionRegistry:BeanDefinition注册类;
         *      把所有需要添加到容器中的bean;调用
         *      BeanDefinitionRegistry.registerBeanDefinition手工注册进来
         */
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            
            boolean definition = registry.containsBeanDefinition("com.atguigu.bean.Red");
            boolean definition2 = registry.containsBeanDefinition("com.atguigu.bean.Blue");
            if(definition && definition2){
                //指定Bean定义信息;(Bean的类型,Bean。。。)
                RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
                //注册一个Bean,指定bean名
                registry.registerBeanDefinition("rainBow", beanDefinition);
            }
        }
    }
    
    //具体的使用方式
    @Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
    
    结果

    5、使用Spring提供的 FactoryBean(工厂Bean)

    • 1)、FactoryBean的接口有三个方法需要实现
    public interface FactoryBean<T> {
    
        /**
         * Return an instance (possibly shared or independent) of the object
         * managed by this factory.
         * <p>As with a {@link BeanFactory}, this allows support for both the
         * Singleton and Prototype design pattern.
         * <p>If this FactoryBean is not fully initialized yet at the time of
         * the call (for example because it is involved in a circular reference),
         * throw a corresponding {@link FactoryBeanNotInitializedException}.
         * <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}
         * objects. The factory will consider this as normal value to be used; it
         * will not throw a FactoryBeanNotInitializedException in this case anymore.
         * FactoryBean implementations are encouraged to throw
         * FactoryBeanNotInitializedException themselves now, as appropriate.
         * @return an instance of the bean (can be {@code null})
         * @throws Exception in case of creation errors
         * @see FactoryBeanNotInitializedException
         */
        T getObject() throws Exception;
    
        /**
         * Return the type of object that this FactoryBean creates,
         * or {@code null} if not known in advance.
         * <p>This allows one to check for specific types of beans without
         * instantiating objects, for example on autowiring.
         * <p>In the case of implementations that are creating a singleton object,
         * this method should try to avoid singleton creation as far as possible;
         * it should rather estimate the type in advance.
         * For prototypes, returning a meaningful type here is advisable too.
         * <p>This method can be called <i>before</i> this FactoryBean has
         * been fully initialized. It must not rely on state created during
         * initialization; of course, it can still use such state if available.
         * <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
         * {@code null} here. Therefore it is highly recommended to implement
         * this method properly, using the current state of the FactoryBean.
         * @return the type of object that this FactoryBean creates,
         * or {@code null} if not known at the time of the call
         * @see ListableBeanFactory#getBeansOfType
         */
        Class<?> getObjectType();
    
        /**
         * Is the object managed by this factory a singleton? That is,
         * will {@link #getObject()} always return the same object
         * (a reference that can be cached)?
         * <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object,
         * the object returned from {@code getObject()} might get cached
         * by the owning BeanFactory. Hence, do not return {@code true}
         * unless the FactoryBean always exposes the same reference.
         * <p>The singleton status of the FactoryBean itself will generally
         * be provided by the owning BeanFactory; usually, it has to be
         * defined as singleton there.
         * <p><b>NOTE:</b> This method returning {@code false} does not
         * necessarily indicate that returned objects are independent instances.
         * An implementation of the extended {@link SmartFactoryBean} interface
         * may explicitly indicate independent instances through its
         * {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean}
         * implementations which do not implement this extended interface are
         * simply assumed to always return independent instances if the
         * {@code isSingleton()} implementation returns {@code false}.
         * @return whether the exposed object is a singleton
         * @see #getObject()
         * @see SmartFactoryBean#isPrototype()
         */
        boolean isSingleton();
    
    }
    
    • 2)、一个类实现此接口
    //创建一个Spring定义的FactoryBean
    public class ColorFactoryBean implements FactoryBean<Color> {
        //返回一个Color对象,这个对象会添加到容器中
        public Color getObject() throws Exception {
            System.out.println("ColorFactoryBean...getObject...");
            return new Color();
        }
        public Class<?> getObjectType() {
            return Color.class;
        }
        //是单例?
        //true:这个bean是单实例,在容器中保存一份
        //false:多实例,每次获取都会创建一个新的bean;
        public boolean isSingleton() {
            return false;
        }
    }
    
    • 3)、在配置类中引入使用
    @Bean
    public ColorFactoryBean colorFactoryBean(){
        return new ColorFactoryBean();
    }
    

    总结点
    1)默认获取到的是工厂bean调用getObject创建的Color对象com.atguigu.bean.Color

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    //工厂Bean获取的是调用getObject创建的对象
    Object bean2 = applicationContext.getBean("colorFactoryBean");
    System.out.println("bean的类型:"+bean2.getClass());
    
    工厂Bean获取的是调用getObject创建的对象

    2)要获取工厂Bean本身,我们需要给id前面加一个"&" &colorFactoryBean

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    //获取工厂Bean本身
    Object bean4 = applicationContext.getBean("&colorFactoryBean");
    System.out.println(bean4.getClass());
    
    获取工厂Bean本身

    可以看到&是定义在BeanFacory中的,加上这个前缀就是返回FacoryBean自己,而不是工厂返回的实例

    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 = "&";
    }
    

    3)在Spring和其他框架进行整合的时候,FacotyBean使用得非常多,这里还要区分FacotyBean和BeanFactory

    相关文章

      网友评论

          本文标题:Spring注解驱动开发系列-注册组件的方式

          本文链接:https://www.haomeiwen.com/subject/zavcphtx.html