zoukankan      html  css  js  c++  java
  • Spring注解驱动开发之Ioc容器篇

      前言:现今SpringBoot、SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解、原理,比如@Conditional、@Import、@EnableXXX等。如果掌握这些底层原理、注解,那么我们对这些高层框架就能做到高度定制,使用的游刃有余

    本篇主要内容:spring IOC 容器的组件添加、组件赋值、组件注入及生命周期

    一、组件注册

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

       过去使用xml配置文件

    	<bean id="person" class="com.atguigu.bean.Person"  scope="prototype" >
    		<property name="age" value="${}"></property>
    		<property name="name" value="zhangsan"></property>
    	</bean>
    

       现在使用@Configuration+@Bean注解

    //配置类==配置文件
    @Configuration  //告诉Spring这是一个配置类
    public class MainConfig {
    	//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
    	@Bean("person")
    	public Person person01(){
    		return new Person("lisi", 20);
    	}
    

      创建spring容器及获取容器中的bean: 

          //使用配置文件注册
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
            Person bean = (Person) applicationContext.getBean("person");
            System.out.println(bean);
            //使用注解
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
            Person bean = applicationContext.getBean(Person.class);
            System.out.println(bean);        

    2、组件注册-@ComponentScan-自动扫描组件&指定扫描规则:

        使用xml配置文件进行自动包扫描添加组件

            <!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component -->
    	  <context:component-scan base-package="com.atguigu" use-default-filters="false"></context:component-scan> 
    

        使用@ComponentScan注解

    @Configuration //前提这个类必须是容器中的组件
    @ComponentScans(
            value = {
                    @ComponentScan(value="com.atguigu",includeFilters = {
                            @Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
                            @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
                            @Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
                    },useDefaultFilters = false)    
            }
            )
    //@ComponentScan  value:指定要扫描的包
    //excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
    //includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
    //FilterType.ANNOTATION:按照注解
    //FilterType.ASSIGNABLE_TYPE:按照给定的类型;
    //FilterType.ASPECTJ:使用ASPECTJ表达式
    //FilterType.REGEX:使用正则指定
    //FilterType.CUSTOM:使用自定义规则
    public class MainConfig {    
        @Bean("person")
        public Person person01(){
            return new Person("lisi", 20);
        }
    
    }

      自定义TypeFilter指定过滤规则:

    public class MyTypeFilter implements TypeFilter {
    
        /**
         * metadataReader:读取到的当前正在扫描的类的信息
         * metadataReaderFactory:可以获取到其他任何类信息的
         */
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                throws IOException {
            // TODO Auto-generated method stub
            //获取当前类注解的信息
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            //获取当前正在扫描的类的类信息
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            //获取当前类资源(类的路径)
            Resource resource = metadataReader.getResource();
            
            String className = classMetadata.getClassName();
            System.out.println("--->"+className);
            if(className.contains("er")){
                return true;
            }
            return false;
        }

    3、@Scope-设置组件作用域 、@Lazy-bean懒加载:

      
        /**
         *
         * @Scope:调整作用域
         * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。
         *                     每次获取的时候才会调用方法创建对象;
         * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。
         *             以后每次获取就是直接从容器(map.get())中拿,
         * request:同一次请求创建一个实例
         * session:同一个session创建一个实例
         * 
         * 懒加载:
         *         单实例bean:默认在容器启动的时候创建对象;
         *         懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化;
         * 
         */
        @Lazy
        @Scope("prototype")
        @Bean("person")
        public Person person(){
            System.out.println("给容器中添加Person....");
            return new Person("张三", 25);
        }  

    4、@Conditional-按照条件注册bean

    /**
         * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean
         * 
         * 如果系统是windows,给容器中注册("bill")
         * 如果是linux系统,给容器中注册("linus")
         */
       @Bean("bill")
        @Conditional(WindowsCondition.class)
        public Person person01(){
            return new Person("Bill Gates",62);
        }
        
        @Conditional(LinuxCondition.class)
        @Bean("linus")
        public Person person02(){
            return new Person("linus", 48);
        }
    
    //判断是否linux系统 public class LinuxCondition implements Condition { /** * ConditionContext:判断条件能使用的上下文(环境) * AnnotatedTypeMetadata:注释信息 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // TODO是否linux系统 //1、能获取到ioc使用的beanfactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //2、获取类加载器 ClassLoader classLoader = context.getClassLoader(); //3、获取当前环境信息 Environment environment = context.getEnvironment(); //4、获取到bean定义的注册类 BeanDefinitionRegistry registry = context.getRegistry(); String property = environment.getProperty("os.name"); //可以判断容器中的bean注册情况,也可以给容器中注册bean boolean definition = registry.containsBeanDefinition("person"); if(property.contains("linux")){ return true; } return false; } }
    //判断是否windows系统 public class WindowsCondition implements Condition { @Override 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; } }

    //注:标注在类上。满足当前条件,这个类中配置的所有bean注册才能生效;
    
    

    5、@Import-给容器中快速导入一个组件

    /**
    * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
    * or regular component classes to import.
    */
    @Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
    //@Import导入组件,id默认是组件的全类名
    public class MainConfig2 {
        
    }
    
    
    //自定义逻辑返回需要导入的组件
    public class MyImportSelector implements ImportSelector {
    
        //返回值,就是到导入到容器中的组件全类名
        //AnnotationMetadata:当前标注@Import注解的类的所有注解信息
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // TODO Auto-generated method stub
            //importingClassMetadata
            //方法不要返回null值
            return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Yellow"};
        }
    
    }
    //使用ImportBeanDefinitionRegistrar
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** * AnnotationMetadata:当前类的注解信息 * BeanDefinitionRegistry:BeanDefinition注册类; * 把所有需要添加到容器中的bean;调用 * BeanDefinitionRegistry.registerBeanDefinition手工注册进来 */ @Override 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); } } }

     6、使用FactoryBean注册组件

    @Bean
        public ColorFactoryBean colorFactoryBean(){
            return new ColorFactoryBean();
        }
    
    //创建一个Spring定义的FactoryBean 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; @Override public boolean isSingleton() { // TODO Auto-generated method stub return false; } }

       

     二、生命周期

     bean的生命周期:
    bean创建---初始化----销毁的过程
    容器管理bean的生命周期:
        我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法

    创建(对象创建)
    单实例:在容器启动的时候创建对象
    多实例:在每次获取的时候创建对象

    1、@Bean指定初始化和销毁方法  

    @Configuration
    public class MainConfigOfLifeCycle {
        
        @Bean(initMethod="init",destroyMethod="detory")
        public Car car(){
            return new Car();
        }
    }
    public class Car {
        
        public Car(){
            System.out.println("car constructor...");
        }
        
        public void init(){
            System.out.println("car ... init...");
        }
        
        public void detory(){
            System.out.println("car ... detory...");
        }
    
    }

    2、InitializingBean和DisposableBean

    @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...");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            // TODO Auto-generated method stub
            System.out.println("cat...afterPropertiesSet...");
        }
    
    }

    3、@PostConstruct&@PreDestroy 

    @Component
    public class Dog implements ApplicationContextAware {
        public Dog(){
            System.out.println("dog constructor...");
        }
        
        //对象创建并赋值之后调用
        @PostConstruct
        public void init(){
            System.out.println("Dog....@PostConstruct...");
        }
        
        //容器移除对象之前
        @PreDestroy
        public void detory(){
            System.out.println("Dog....@PreDestroy...");
        }   
    
    }

    4、BeanPostProcessor-后置处理器

    **
     * 后置处理器:初始化前后进行处理工作
     * 将后置处理器加入到容器中
     * @author lfy
     */
    @Component
    public class MyBeanPostProcessor implements BeanPostProcessor {
    
        @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;
        }
    
    }

    BeanPostProcessor原理分析:

      在postProcessBeforeInitialization打个断点,然后debug运行,方法栈如下:

        

      由上而下分析方法栈中的主要方法,先来看applyBeanPostProcessorsBeforeInitialization方法:

        public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
                throws BeansException {
    
            Object result = existingBean;
        //遍历所有的BeanPostProcessor 并执postProcessBeforeInitialization方法,如果返回null,则停止遍历直接返回;
            for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
                result = beanProcessor.postProcessBeforeInitialization(result, beanName);
                if (result == null) {
                    return result;
                }
            }
            return result;
        }

      接着往下看initializeBean方法中的部分源码:

    protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
         ...
            Object wrappedBean = bean;
            if (mbd == null || !mbd.isSynthetic()) {
            //前一步 wrappedBean
    = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try {
           //这里调用了所有初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); }
    catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) {
            //在上面执行了所有初始化方法后 最后再遍历执行所有BeabPostProcessors的postProcessAfterInitialization方法 wrappedBean
    = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }

      再往下看doCreateBean方法中的部分代码:

    // Initialize the bean instance.
            Object exposedObject = bean;
            try {
            //给bean进行属性赋值(getter) populateBean(beanName, mbd, instanceWrapper);
    if (exposedObject != null) { exposedObject = initializeBean(beanName, exposedObject, mbd); } }

    大概的过程:                                                                                                      

    * populateBean(beanName, mbd, instanceWrapper);在初始化开始之前 给bean进行属性赋值
    * initializeBean
    * {
    * applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    * invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
    * applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

      }

    定制bean的生命周期: 

    * 1)、指定初始化和销毁方法;
    * 通过@Bean指定init-method和destroy-method;
    * 2)、通过让Bean实现InitializingBean(定义初始化逻辑),
    * DisposableBean(定义销毁逻辑);
    * 3)、可以使用JSR250;
    * @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
    * @PreDestroy:在容器销毁bean之前通知我们进行清理工作
    * 4)、BeanPostProcessor【interface】:bean的后置处理器;
    * 在每个bean初始化前进行一些处理工作;
    * postProcessBeforeInitialization:在初始化之前工作(其他初始化方法调用之前调用)
    * postProcessAfterInitialization:在初始化之后工作(其他初始化方法调用结束以后调用)
     

    三、属性赋值

    1、@Value赋值

    //使用@Value赋值;
        //1、基本数值
        //2、可以写SpEL; #{}
        //3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值)
        
        @Value("张三")
        private String name;
        @Value("#{20-2}")
        private Integer age;
        
        @Value("${person.nickName}")
        private String nickName;

    2、@PropertySource加载外部配置文件

    //使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值
    @PropertySource(value={"classpath:/person.properties"})
    @Configuration
    public class MainConfigOfPropertyValues {
        
        @Bean
        public Person person(){
            return new Person();
        }
    
    }

    四、自动装配

      Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值

    1、@Autowired&@Qualifier&@Primary

      @Autowired:自动注入:默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值,如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找applicationContext.getBean("bookDao")

      @Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名,自动装配默认一定要将属性赋值好,没有就会报错, 使用@Autowired(required=false); 

      @Primary:让Spring进行自动装配的时候,默认使用首选的bean也可以继续使用,@Qualifier指定需要装配的bean的名字     

       @Primary
        @Bean("bookDao2")
        public BookDao bookDao(){
            BookDao bookDao = new BookDao();
            bookDao.setLable("2");
            return bookDao;
        }

    2、@Resource&@Inject 

      Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]

        @Resource:可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的,没有能支持@Primary功能没有支持@Autowired(reqiured=false);

      @Inject:需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;

    3、方法、构造器位置的自动装配 

    @Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值
    * 1)、[标注在方法位置]:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配
    * 2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取
    * 3)、放在参数位置:

    /**
    * @Bean标注的方法创建对象的时候,方法参数的值从容器中获取
    * @param car
    * @return
    */
      @Bean
        public Color color(Car car){
            Color color = new Color();
            color.setCar(car);
            return color;
        }

    4、Aware注入Spring底层组件&原理

      自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx),自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware接口把Spring底层一些组件注入到自定义的Bean中,xxxAware:功能底层使用了xxxProcessor,ApplicationContextAware==》ApplicationContextAwareProcessor;(后续分析源码,会细讲)

    5、@Profile根据环境注册bean 

    Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能: 

      1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
      2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
      3)、没有标注环境标识的bean在,任何环境下都是加载的;

        @Profile("test")
        @Bean("testDataSource")
        public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setUser(user);
            dataSource.setPassword(pwd);
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
            dataSource.setDriverClass(driverClass);
            return dataSource;
        }
        
        
        @Profile("dev")
        @Bean("devDataSource")
        public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setUser(user);
            dataSource.setPassword(pwd);
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
            dataSource.setDriverClass(driverClass);
            return dataSource;
        }
        
        @Profile("prod")
        @Bean("prodDataSource")
        public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setUser(user);
            dataSource.setPassword(pwd);
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515");
            
            dataSource.setDriverClass(driverClass);
            return dataSource;
        }

    激活环境方式:

      1)使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test

      2)代码的方式激活某种环境:   

    AnnotationConfigApplicationContext applicationContext = 
                    new AnnotationConfigApplicationContext();
            //1、创建一个applicationContext
            //2、设置需要激活的环境
            applicationContext.getEnvironment().setActiveProfiles("dev");
            //3、注册主配置类
            applicationContext.register(MainConfigOfProfile.class);
            //4、启动刷新容器
            applicationContext.refresh();

                                                   
      

                                     

     
  • 相关阅读:
    如何在 SQLServer 中启用 xp_cmdshell 等
    强力解决使用node版本管理工具 NVM 出现的问题(找不到 node,或者找不到 npm)
    [啃书] 预告篇
    [啃书] 第5篇
    [啃书] 第4篇
    [啃书] 第3篇
    [啃书] 第2篇
    [啃书] 第1篇
    [前端随笔][Vue] 多级菜单实现思路——组件嵌套
    [算法小练][图][拓扑排序+深度优先搜索] 平板涂色问题
  • 原文地址:https://www.cnblogs.com/qzlcl/p/10940263.html
Copyright © 2011-2022 走看看