zoukankan      html  css  js  c++  java
  • (一)spirng IOC、DI的详细应用

    一、spring核心概念 IOC和DI

      IOC即控制反转,这是一种设计思想,有反转既有正转,那么什么是反转什么是正转呢?

    • 控制反转控制反转,首先是控制,再者是反转。
    • 所谓控制,在传统JAVA SE设计标准中,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
    • 所谓反转,有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
    • 所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

        那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

    • IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

        理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。

    二、Spring IOC 容器底层注解使用

      配置spring 容器有多种形式,起初是以xml配置文件为主流,这一方式复杂且繁琐,后期代码量大的时候难以维护。后来注解形式的配置变成了为主流,springboot便是其集大成者。

    1.   xml配置文件的形式

    <?xml version="1.0" encoding="UTF-8"?> 
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi
    ="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation
    ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      /定义一个Bean的信息   
    <bean id="car" class="com.tuling.compent.Car"></bean>
    </beans>
    去容器中读取Bean
    public static void main( String[] args ) {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    System.out.println(ctx.getBean("person"));
    }

      2. 基于读取配置类的形式定义Bean信息

    •   定义配置类,配置类可以直接定义一些列的bean,也可以通过扫描的形式加入bean
    @Configuration 
    public class MainConfig {
      @Bean
      public Person person(){
       return new Person(); 
      } 
    }
    • 在配置类上写@CompentScan注解来进行包扫描,这种形式可以排除或者指定某些bean。
    @Configuration
     @ComponentScan(basePackages = {"com.tuling.testcompentscan"})
     public class MainConfig {
     }
      •   排除用法 excludeFilters(排除@Controller注解的,和TulingService的),
    @Configuration 
    @ComponentScan(basePackages = {"com.tuling.testcompentscan"},excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}), 
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {TulingService.class}) })
    public class MainConfig {
     }
      •   包含用法 includeFilters ,注意,若使用包含的用法,需要把useDefaultFilters属性设置为false(true表示扫描全部的)
    @Configuration 
    @ComponentScan(basePackages = {"com.tuling.testcompentscan"},includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, Service.class}) },useDefaultFilters = false) 
    public class MainConfig { 
    }
      • @ComponentScan.Filter type的类型

       a)注解形式的FilterType.ANNOTATION @Controller @Service @Repository @Compent

       b)指定类型的 FilterType.ASSIGNABLE_TYPE @ComponentScan.Filter(type =FilterType.ASSIGNABLE_TYPE,value = {TulingService.class})
       c)aspectj类型的 FilterType.ASPECTJ(不常用)
       d)正则表达式的 FilterType.REGEX(不常用)
       e)自定义的 FilterType.CUSTOM
       其中,ANNOTATION 和 ASSIGNABLE_TYPE 区别在于,后者一次性只能指定一个类,而前者包含所有带有该注解的类。
        •     FilterType.CUSTOM 自定义类型如何使用
    public class TulingFilterType implements TypeFilter {
        @Override
        public boolean match(MetadataReader metadataReader,
            MetadataReaderFactory metadataReaderFactory) throws IOException {
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            Resource resource = metadataReader.getResource();
    
            if (classMetadata.getClassName().contains("dao")) {
                return true;
            }
    
            return false;
        }
    }
    @ComponentScan(basePackages =  {
        "com.tuling.testcompentscan"}
    , includeFilters =  {
        @ComponentScan.Filter(type = FilterType.CUSTOM, value = TulingFilterType.class)
    }
    , useDefaultFilters = false)
    public class MainConfig {
    }
    • 去容器中读取Bean的信息(传入配置类)
    public static void main( String[] args ) { 
      AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class); 
      System.out.println(ctx.getBean("person"));
     }

       

      3.配置Bean的作用域对象

    • @Scope指定的作用域方法取值,其中单例和多例多常用,最后两个几乎用不到
    a) singleton 单实例的(默认) 
    b) prototype 多实例的 
    c) request 同一次请求 
    d) session 同一个会话级别
    • 在不指定@Scope的情况下,所有的bean都是单实例的bean,而且是饿汉加载(容器启动实例就创建好了)
    @Bean 
    public Person person() { return new Person(); }
    • 指定@Scope为 prototype表示为多实例的,而且还是懒汉模式加载(IOC容器启动的时候,并不会创建对象,而是在第一次使用的时候才会创建)
    @Bean 
    @Scope(value = "prototype")
     public Person person() { 
    return new Person();
    }
    • Bean的懒加载@Lazy(主要针对单实例的bean 容器启动的时候,不创建对象,在第一次使用的时候才会创建该对象)
    @Bean 
    @Lazy
     public Person person() { return new Person(); }
    • @Conditional进行条件判断等.
      场景,有二个组件TulingAspect 和TulingLog ,我的TulingLog组件是依赖于TulingAspect的组件
           应用:自己创建一个TulingCondition的类 实现Condition接口 
    public class TulingCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
            if (context.getBeanFactory().containsBean("tulingAspect")) {
                return true;
            }
    
            return false;
        }
    }
    public class MainConfig {
        @Bean
        public TulingAspect tulingAspect() {
            return new TulingAspect();
        }
    
    //当切 容器中有tulingAspect的组件,那么tulingLog才会被实例化.
        @Bean
        @Conditional(value = TulingCondition.class)
        public TulingLog tulingLog() {
            return new TulingLog();
        }
    }

      4.往IOC 容器中添加组件的方式

    • ①:通过@CompentScan +@Controller @Service @Respository @componet

        适用场景: 针对我们自己写的组件可以通过该方式来进行加载到容器中。

    • ②:通过@Bean的方式来导入组件(适用于导入第三方组件的类)
    • ③:通过@Import来导入组件 (导入组件的id为全类名路径)
    @Configuration
    @Import(value =  {
        Person.class, Car.class}
    )
    public class MainConfig {
    }
      •   通过@Import 的ImportSeletor类实现组件的导入 (导入组件的id为全类名路径) 
    public class TulingImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[] { "com.tuling.testimport.compent.Dog" };
        }
    }
    @Configuration
    @Import(value =  {
        Person.class, Car.class, TulingImportSelector.class}
    )
    public class MainConfig {
    }
      •   通过@Import的 ImportBeanDefinitionRegister导入组件 (可以指定bean的名称) 
    public class TulingBeanDefinitionRegister  implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {
          //创建一个bean定义对象
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Cat.class);
          //把bean定义对象导入到容器中
            registry.registerBeanDefinition("cat", rootBeanDefinition);
        }
    }
    @Configuration
    @Import(value =  {
        Person.class, Car.class, TulingImportSelector.class, TulingBeanDefinitionRegister.class}
    )
    public class MainConfig {
    }
    • ④:通过实现FacotryBean接口来实现注册组件 
    public class CarFactoryBean implements FactoryBean<Car> {
        @Override
        public Car getObject() throws Exception {
            return new Car();
        }
    
    /返回bean的类型
        @Override
        public Class<?> getObjectType() {
            return Car.class;
        }
    /是否为单例
        @Override
        public boolean isSingleton() {
            return true;
        }
    }

      5.BeanFactory和ApplicationContext(bean工厂和应用上下文)

       

    • Bean 工厂(com.springframework.beans.factory.BeanFactory)是Spring 框架最核心的接口,它提供了高级IoC 的配置机制。
    • 应用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory 基础之上。

         由上图可以知道,BeanFactory是最顶层接口,而ApplicationContext则集成BeanFactory,增加了许多高级特性。

    • ApplicationContext 的初始化和BeanFactory 有一个重大的区别:BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean 时才实例目标Bean;而ApplicationContext 则在初始化应用上下文时就实例化所有单实例的Bean 

      6.bean的生命周期

    • 指定bean的初始化和销毁方法    
      bean的创建----->初始化----->销毁方法
      •   针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法
      •   针对多实例bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的时候被创建,而且bean的销毁不受IOC容器的管理. 
      由容器管理Bean的生命周期,我们可以通过自己指定bean的初始化方法和bean的销毁方法
    @Configuration
    public class MainConfig { //指定了bean的生命周期的初始化方法和销毁方法.
        @Bean(initMethod = "init", destroyMethod = "destroy")
        public Car car() {
            return new Car();
        }
    }
    • 通过 InitializingBean和DisposableBean 的二个接口实现bean的初始化以及销毁方法
    @Component
    public class Person implements InitializingBean, DisposableBean {
        public Person() {
            System.out.println("Person的构造方法");
        }
    
        @Override
        public void destroy() throws Exception {
            System.out.println("DisposableBean的destroy()方法 ");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("InitializingBean的 afterPropertiesSet方法");
        }
    }
    • :通过JSR250规范 提供的注解@PostConstruct 和@ProDestory标注的方法
    @Component
    public class Book {
        public Book() {
            System.out.println("book 的构造方法");
        }
    
        @PostConstruct
        public void init() {
            System.out.println("book 的PostConstruct标志的方法");
        }
    
        @PreDestroy
        public void destory() {
            System.out.println("book 的PreDestory标注的方法");
        }
    }
    • 通过Spring的BeanPostProcessor的 bean的后置处理器会拦截所有bean创建过程 
      •   postProcessBeforeInitialization  在init方法之前调用
      •   postProcessAfterInitialization  在init方法之后调用
    @Component
    public class TulingBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
            System.out.println(
                "TulingBeanPostProcessor...postProcessBeforeInitialization:" +
                beanName);
    
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
            System.out.println(
                "TulingBeanPostProcessor...postProcessAfterInitialization:" +
                beanName);
    
            return bean;
        }
    }
        BeanPostProcessor的执行时机 
    populateBean(beanName, mbd, instanceWrapper)
    initializeBean{ 
      applyBeanPostProcessorsBeforeInitialization() invokeInitMethods{ isInitializingBean.afterPropertiesSet
       自定义的init方法 
      }
      applyBeanPostProcessorsAfterInitialization()方法
     }
    • 自动装配
      •     @AutoWired的使用 
    //一个Dao
     @Repository
     public class TulingDao {
    } @Service
    public class TulingService {    @Autowired    private TulingDao tulingDao; }
    结论:
      a:自动装配首先时按照类型进行装配,若在IOC容器中发现了多个相同类型的组件,那么就按照 属性名称来进行装配
    @Autowired
    private TulingDao tulingDao;
      比如,我容器中有二个TulingDao类型的组件 一个叫tulingDao 一个叫tulingDao2
      那么我们通过@AutoWired 来修饰的属性名称时tulingDao,那么拿就加载容器的tulingDao组件,若属性名称为tulignDao2 那么他就加载的时tulingDao2组件
     
      b:假设我们需要指定特定的组件来进行装配,我们可以通过使用@Qualifier("tulingDao")来指定装配的组件或者在配置类上的@Bean加上@Primary注解 
    @Autowired 
    @Qualifier("tulingDao") 
    private TulingDao tulingDao2;
      •     @Resource(JSR250规范) 
        功能和@AutoWired的功能差不多一样,但是不支持@Primary 和@Qualifier的支持 
      •     @InJect(JSR330规范)
        需要导入jar包依赖,功能和支持@Primary功能 ,但是没有Require=false的功能
      •   使用autowired 可以标注在方法上 
            标注在set方法上 
    @Autowired
     public void setTulingLog(TulingLog tulingLog) { 
      this.tulingLog = tulingLog; 
    }
            标注在构造方法上
    @Autowired
     public TulingAspect(TulingLog tulingLog) {
       this.tulingLog = tulingLog;
     }
      •   我们自己的组件 需要使用spring ioc的底层组件的时候,比如 ApplicationContext等 
        我们可以通过实现XXXAware接口来实现
    @Component
    public class TulingCompent implements ApplicationContextAware, BeanNameAware {
        private ApplicationContext applicationContext;
    
        @Override
        public void setBeanName(String name) {
            System.out.println("current bean name is :【" + name + "】");
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
            this.applicationContext = applicationContext;
        }
    }
  • 相关阅读:
    2.六角星绘制
    1.五角星绘制
    Redis
    javaScript
    反射
    区分'方法'和'函数'
    递归,二分法
    匿名函数,排序函数,过滤函数,映射函数,
    生成器,生成器函数,推导式,生成器表达式.
    函数,闭包,迭代器
  • 原文地址:https://www.cnblogs.com/shyroke/p/14532664.html
Copyright © 2011-2022 走看看