• 02-Spring与IOC


    二、Spring与IOC

    1. 控制反转IOC的概述

    控制反转(IOC:Inversion of Control)是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。
    IOC是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式有两种:依赖注入和依赖查找。依赖注入的方式应用更加广泛。

    • 依赖注入【DI:Dependency Injection,DI】:是指在程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
    • 依赖查找【DL:Dependency Lookup,DL】:容器提供回调接口和上下文环境给组件,程序代码则需要提供具体的查找方式。比较典型的是依赖与JNDI服务接口(Java Naming and Directory Interface)的查找。
      依赖注入是目前最优秀的解耦方式。依赖注入让Spring的bean之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起的。

    总结:
    IOC控制反转就是将对象的创建全给了Spring容器。
    DI依赖注入的前提是有IOC的环境,Spring创建这个类的过程中,Spring将类依赖的属性设置进去。

    2. Spring的工厂类

    Bean组件主要解决:Bean的定义,Bean的创建以及对Bean的解析。
    开发者关心Bean的创建,其他由Spring内部帮你完成。
    (1)Bean的创建是典型的工厂模式,它的顶级接口是BeanFactory,

    ApplicationContext工厂:

    ApplicationContext用于加载Spring的配置文件,在程序中充当“容器”的角色,其实现类有两个,通过ctrl+t查看。
    A:配置文件在类路径下:使用ClassPathXmlApplicationContext实现类进行加载。
    B:配置文件在本地目录中或者在项目根路径下:使用FileSystemXmlApplicationContext实现类进行加载。

    	@Test
    	public void classPathXmlTest() {
    		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    		IStudentService studentService = (IStudentService) ac.getBean("studentService");
    		studentService.some();
    	}
    	@Test
    	public void FileSystemXmlTest() {
    		ApplicationContext ac = new FileSystemXmlApplicationContext("d:/applicationContext.xml");
    		IStudentService studentService = (IStudentService) ac.getBean("studentService");
    		studentService.some();
    	}
    
    BeanFactory工厂:

    BeanFactory接口对象也可作为Spring容器出现。BeanFactory接口是ApplicationContext接口的父类。
    若要创建BeanFactory容器,需要使用其实现类XmlBeanFactory,该类可以加载Spring配置文件。而Spring配置文件以资源Resource的形式出现在XmlBeanFactory类的构造器参数中。Resource是一个接口,其具体有两个实现类:
    A:ClassPathResource:指定类路径下的资源文件
    B:FileSystemResource:指定项目根路径或者本地磁盘路径下的资源文件。

    	@Test
    	public void BeanFactoryTest() {
    		BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    		IStudentService studentService = (IStudentService) factory.getBean("studentService");
    		studentService.some();
    	}
    
    BeanFactory和ApplicationContext的区别:

    虽然这两个接口容器所要加载的Spring配置文件是同一个文件,但在代码中的这两个容器对象却不是同一个对象,即不是同一个容器:它们对于容器内对象的装配(创建)时机是不同的。

    ApplicationContext容器中对象的装配时机:

    ApplicationContext容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可,执行效率比较高,但占用内存。

    ApplicationContext容器中对象的装配时机:

    BeanFactory容器,对容器中对象的装配与加载采用延迟加载策略,即在第一次调用getBean()时,才真正装配该对象。

    	@Test
    	public void BeanFactoryTest() {
    		// 获取容器:此时容器中的对象还未进行装配
    		BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    		// 执行下面语句时,会对studentService对象进行装配
    		IStudentService studentService = (IStudentService) factory.getBean("studentService");
    		studentService.some();
    	}
    

    3. Bean的装配

    Bean的装配,即Bean对象的创建。容器根据代码要求创建Bean对象后再传递给代码的过程,称为Bean的装配。
    装配的方式:默认形式、

    • 默认装配方式
      代码通过getBean()方法从容器中获取指定的Bean实例,容器首先会调用Bean类的无参构造器,利用反射,创建空值的实例对象。
    • 动态工厂Bean
      有些时候,项目中需要通过工厂类来创建Bean实例,而不能像前面例子中似的,直接由Spring容器来装配Bean实例。使用工厂模式创建Bean实例,就会使工厂类与要创建的Bean类耦合到一起。
      (1)动态工厂Bean作为普通Bean使用
      将动态工厂Bean作为普通Bean来使用是指,在配置文件中注册动态工厂Bean后,测试类直接通过getBean()获取到工厂对象,再由工厂对象调用其相应方法创建相应的目标对象。配置文件中无需注册目标对象的Bean。因为目标对象的创建不由Spring容器来管理。
      但这样做的缺点是:不仅工厂类与目标类耦合到一起了,测试类与工厂类也耦合到一起了。
    public class ServiceFactory{
        public IUserService getUserService(){
            return new UserServiceImpl();
        }
    }
    
    <bean id="factory" class="com.zhy.service.ServiceFactory" />
    
    @Test
    public void factoryTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 从Spring容器中获取factory
        ServiceFactory factroy = ac.getBean("factory");
        IUserService userService = factory.getUserService();
        userService.doSome();
    }
    

    (2)使用Spring的动态工厂Bean
    Spring对于使用动态工厂来创建的Bean,由专门的属性定义。
    factory-bean指定相应的工厂Bean,由factory-method指定创建所用方法。此时配置文件中至少会有两个bean的定义:工厂类的bean,与工厂类所要创建的目标类Bean。而测试类中不再需要获取工厂Bean对象了,可以直接获取目标Bean对象,实现测试类与工厂类间的解耦。

    <bean id="factory" class="com.zhy.service.ServiceFactory" />
    <bean id="userService" factory-bean="factory" factory-method="getUserService" />
    
    @Test
    public void springFactoryTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserService userService = ac.getBean("userService");
        userService.doSome();
    }
    

    (3)静态工厂Bean
    使用工厂模式中的静态工厂来创建实例Bean。
    此时需要注意:静态工厂无需工厂实例,所以不再需要定义静态工厂
    而对于工厂所要创建的Bean,其实不是由自己的类创建的,所以无需指定自己的类。但其实是由工厂类创建的,所以需要指定所用工厂类,故class属性指定的是工厂类而非自己的类。当然,还需要通过factory-method属性指定工厂方法。

    public class ServiceFactory{
        public static IUserService getUserService(){
            return new UserServiceImpl();
        }
    }
    
    <bean id="userService" class="com.zhy.service.ServiceFactory" factory-method="getUserService" />
    

    4. 容器中Bean的作用域

    当通过Spring容器创建一个Bean实例时,不仅可以完成Bean的实例化,还可以通过scope属性,为Bean指定特定的作用域。Spring支持五种作用域。
    (1)singleton:单例模式。即在整个Spring容器中,使用Singleton定义的Bean将是单例的,只有一个实例,默认为单例的。
    (2)prototype:多例模式。即每次使用getBean()方法获取实例都是一个新的实例。
    (3)request:对于每次HTTP请求,都将会产生一个不同的Bean实例。
    (4)session:对于每个不同的HTTP session,都将会产生一个不同的Bean实例。
    (5)global-session:

    注意:
    1.对于scope的值为request、session、global-session,只有在Web应用是使用Spring时,该作用于才有效。
    2.对于scope的值为singleton的单例模式,该bean是在容器被创建时就被装配好了。
    3.对于scope的值为prototype的原型模式,Bean实例是在代码中使用该Bean实例时才进行装配的。

    5. Bean的后处理器

    Bean后处理器是一种特殊的Bean,容器中所有的Bean在初始化时,均会自动执行该类的两个方法。由于该Bean是由其他Bean自动调用执行,不是程序员手工调用,故此Bean无需id属性。
    需要做的是,在Bean后处理器类方法中,只要对Bean类与Bean类中的方法进行判断,就可实现对指定的Bean的指定方法进行功能扩展与增强。方法返回地Bean对象,即是增强过的对象。
    代码中需要自定义Bean后处理器类。该类是实现了接口BeanPostProcessor的类。该接口中包含两个方法,分别在目标Bean初始化完毕之前与之后执行。它们的返回值为:功能被扩展或增强后的Bean对象。
    Bean初始化完毕有一个标志,一个方法将被执行。即当该方法被执行时,表示该Bean被初始化完毕。所以,Bean后处理器中两个方法的执行,是在这个方法之前之后执行。这个方法在后面将会讲到。
    public Object postProcessBeforeInitialzation(Object bean,String beanId) throws BeansException
    该方法会在目标Bean初始化完毕之前由容器自动调用。
    public Object postProcessAfterInitialzation(Object bean,String beanId) throws BeansException
    该方法会在目标Bean初始化完毕之后由容器自动调用。
    它们的参数是:第一个参数是系统即将初始化的Bean实例,第二个参数是该Bean实例的id属性值。若Bean没有id就是name属性值。

    举例:
    程序中有一个业务接口IService,其中两个业务方法some()和other()。有两个Bean:StudentServiceImpl和TeacherServiceImpl(),均实现了IService接口。
    要求:对StudentServiceImpl的some()方法进行增强,输出其开始执行时间与执行结束时间。

    public interface IService{
        void some();
        void other();
    }
    public class StudentServiceImpl implements IService{
        @Override
        public void some(){
            System.out.println(this.getClass().getSimpleName()+",执行some()方法");
        }
        @Override
        public void other(){
            System.out.println(this.getClass().getSimpleName()+",执行other()方法");
        }
    }
    public class MyBeanPostProcessor implements BeanPostProcessor{
        @Override 
        public Object postProcessBeforeInitialization(Object bean,String beanName){
            System.out.println("执行postProcessBefreInitialization()");
            // 即使不对bean进行增强,也要使用方法返回bean,不能为默认的null,否则将抛出空指针异常。
            return bean;
        }   
        @Override 
        public Object postProcessAfterInitialization(final Object bean,String beanName){
            if("studentService".equals(beanName)){
                Proxu.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),new InvocationHandler(){
        public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
            if("some".equals(method.getName())){
                System.out.println("目标方法执行开始时间:"+System.currentTimeMillis());
                // 执行目标方法
                Object result = method.invoke(bean,args);
                System.out.println("目标方法执行结束时间:"+System.currentTimeMillis());
                return result;
            }
            return method.invoke(bean,args);
        }
    });
    return proxy;
            }
        return bean;
        } 
    }
    
    <bean id="studentService" class="com.zhy.service.impl.StudentServiceImpl"/>
    <bean id="teacherService" class="com.zhy.service.impl.TeacherServiceImpl"/>
    <bean class="com.zhy.MyBeanPostProcessor"/>
    
    @Test
    public void factoryTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 从Spring容器中获取factory
        IService studentService= (IService)ac.getBean("studentService");
        studentService.some();
        studentService.other();
        IService teacherService= (IService)ac.getBean("teacherService");
        teacherService.some();
        teacherService.other();
    }
    

    6. 定制Bean的生命始末

    可以为Bean定制初始化后的生命行为,也可以为Bean定制销毁前的生命行为。
    步骤:

    1. 这些方法需要在Bean类中实现定义好:方法名是随意的public void方法。
    2. 其次在配置文件的标签中添加如下属性
      • init-method:指定初始化方法的方法名
      • destory-method:指定销毁方法的方法名

    注意:如果希望看到Bean的destory-method的执行结果,需要满足两个条件:
    (1)Bean为singleton,即单例的。
    (2)要确保容器关闭,接口ApplicationContext没有close()方法,但其实现类有。所以,可以将ApplicationContext强转为其实现类对象,或者直接创建的就是实现类对象。

    <bean id="userService" class="com.zhy.service.impl.UserServiceImpl" 
        init-method="setUp" destory-method="tearDown"/>
    
    @Test
    public void destoryMethodTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserService userService = (IUserService) ac.getBean("userService");
        userService.doSome();
        userService.doOther();
        // 关闭容器对象
        ((ClassPathXmlApplicationContext)ac).close();
    }
    

    7. Bean的生命周期

    Bean实例从创建到最后销毁,需要经过很多过程,执行很多生命周期方法。

    1. 调用无参构造器,创建实例对象。
    2. 调用参数的setter,为属性注入值。
    3. 若Bean实现了BeanNameAware接口,则会执行接口方法setBeanName(String beanId),使bean类可以获取其在容器中的id名称。
    4. 若Bean实现了BeanFactoryAware接口,则执行接口方法setBeanFactory(BeanFactory factory),使Bean类可以获取到BeanFactory对象。
    5. 若定义并注册了Bean后处理器BeanPostProcessor,则执行接口方法postProcessBeforeInitialization()。
    6. 若Bean实现了InitializingBean接口,则执行接口afterPropertiesSet()。该方法在Bean的所有属性的set方法执行完毕后执行,是Bean初始化结束的标志,即Bean实例化结束。
    7. 若设置了init-method方法,则执行。
    8. 若定义并注册了Bean后处理器BeanPostProcssor,则执行接口方法postProcessAfterInitialization()。
    9. 执行业务方法。
    10. 若Bean实现了DisposableBean接口,则执行接口方法destory()。
    11. 若设置了destory-method方法,则执行。
  • 相关阅读:
    林大妈的JavaScript进阶知识(二):JS异步行为
    前端技术基础(一):浏览器相关
    林大妈的JavaScript进阶知识(一):对象与内存
    林大妈的JavaScript进阶知识(四):HTML5 History API
    林大妈的CSS知识清单(二)可见格式化模型(内含margin塌陷与浮动闭合的解决方案)
    林大妈的CSS知识清单(一)添加样式
    林大妈的JavaScript基础知识(三):JavaScript编程(4)数组
    林大妈的JavaScript基础知识(三):JavaScript编程(3)原型
    林大妈的JavaScript基础知识(三):JavaScript编程(2)函数
    基于vue-cli快速构建
  • 原文地址:https://www.cnblogs.com/zhy0720/p/10449864.html
Copyright © 2020-2023  润新知