spring中最常用的控制反转和面向切面编程 - Go语言中文社区

spring中最常用的控制反转和面向切面编程


spring中最常用的控制反转和面向切面编程。

一、IOC

  IoC(Inversion of Control,控制倒转)。对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

  所有的类都会在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就是通过反射来实现注入的。

  在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。

  所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。

二、IOC的好处

  可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。代码中的每一个Class都可以单独测试,彼此之间互不影响,只要保证自身的功能无误即可,这就是组件之间低耦合或者无耦合带来的好处。

  每个开发团队的成员都只需要关心实现自身的业务逻辑,完全不用去关心其它的人工作进展,因为你的任务跟别人没有任何关系,你的任务可以单独测试,你的任务也不用依赖于别人的组件,再也不用扯不清责任了。

  可复用性好,我们可以把具有普遍性的常用组件独立出来,反复利用到项目中的其它部分,或者是其它项目,当然这也是面向对象的基本特征。

  IOC生成对象的方式转为外置方式,也就是把对象生成放在配置文件里进行定义,这样,当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的特性。

三、IOC常见的注入方式

  接口注入(Spring不支持),接口注入模式因为历史较为悠久,在很多容器中都已经得到应用。但由于其在灵活性、易用性上不如其他两种注入模式,因而在 IOC 的专题世界内并不被看好。

  setter注入,对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。 如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用。

  构造器注入,在构造期间完成一个完整的、合法的对象。 所有依赖关系在构造函数中集中呈现。 依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。 只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。

四、AOP

  AOP(Aspect-OrientedProgramming,面向切面编程)。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

  而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系。

  使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

  实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

五、AOP概念

  切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。切面用spring的 Advisor或拦截器实现。

  连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

  通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice。

  切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上。

  引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。

  目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

  AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

  织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入

六、AOP通知类型

  前置通知(before advice):在切入点之前执行。

  后置通知(after returning advice):在切入点执行完成后,执行通知。

  环绕通知(around advice):包围切入点,调用方法前后完成自定义行为。

  异常通知(after throwing advice):在切入点抛出异常后,执行通知。

七、Spring启动过程

  通过对ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");这条语句的跟踪,最重要的是调用了AbstractApplicationContext类的refresh()方法。refresh()方法有以下几个步骤:

  1.第一个方法是prepareRefresh(),prepareRefresh()只包含了两个方法,这两个方法加载了服务器的一些信息和判断必填项是否都完整。第二个方法:ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();refreshBeanFactory()这个方法,如果容器现在已经启动,而我们要新启动一个容器,那么首要的就是销毁以前的容器,清空容器和容器中注册了的bean。新建容器对象DefaultListableBeanFactory beanFactory = createBeanFactory();新建一个DefaultListableBeanFactory.注意在这里面有一个方法getInternalParentBeanFactory(),这个方法获得并设置了父容器。当然在spring启动的过程中这个父容器是空的。

  2.判断bean是否允许覆盖,bean是否允许循环引用,把java注解和spring标准注解都放到了容器里面来。

  3.要加载bean loadBeanDefinitions(beanFactory)。实现了该方法的类一共有三个1、XmlWebApplicationContext 2、AbstractXmlApplicationContext 3、AnnotationConfigWebApplicationContext.在项目启动的过程中XmlWebApplicationContext真正的被执行。接下来的bean加载过程主要涉及到的就是配置文件解析。配置文件解析大体步骤1、根据命名空间将标签名称和解析类放置到map中2、读取配置文件的过程中遇到标签就将找到解析类去解析。

八、如何感知到Spring容器启动成功这件事情?

  spring提供了事件监听器的处理机制,spring提供了内置的几类的事件:

    ContextClosedEvent 当使用ConfigurableApplicationContext接口的close()方法关闭ApplicationContext容器时触发该事件。

    ContextRefreshedEvent ApplicationContext容器初始化或者刷新时触发该事件。

    ContextStartedEvent 当使用ConfigurableApplicationContext接口的start()方法启动ApplicationContext容器时触发该事件。

    ContextStoppedEvent 当使用ConfigurableApplicationContext接口的stop()方法停止ApplicationContext容器时触发该事件。

    RequestHandleEvent。

在spring容器启动完成后会触发ContextRefreshedEvent事件,在spring容器启动过程中调用AbstractApplicationContext的refresh()方法,其中调用了finishRefresh()用来发布这个事件。

ApplicationEventMulticaster在接收到ApplicationEvent事件之后,通过multicastEvent方法,通知所有的观察者ApplicationListener。

比如hsf中通过创建一个ContextRefreshedEvent,ContextClosedEvent事件监听器,在spring容器启动完成后和容器关闭时,做一些处理动作。

九、FactoryBean与BeanFactory

  Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean,这两种Bean都被容器管理,但工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,其返回的是该FactoryBean的getObject方法所返回的对象。

  BeanFactory是IoC容器的核心接口。它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。从本质上讲,BeanFactory仅仅只是一个维护bean定义以及相互依赖关系的高级工厂接口。通过BeanFactory我们可以访问bean定义。

十、如何在Bean初始化前后做一些事情

  首先看下AbstractAutowireCapableBeanFactory的createBean方法:(删去些占地方的try catch)。

复制代码
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException {
    resolveBeanClass(mbd, beanName); /1解析Bean的class
    mbd.prepareMethodOverrides(); //2 方法注入准备
    Object bean = resolveBeforeInstantiation(beanName, mbd); //3 第一个BeanPostProcessor扩展点
    if (bean != null) { //4 如果3处的扩展点返回的bean不为空,直接返回该bean,后续流程不需要执行
        return bean;
    }
    Object beanInstance = doCreateBean(beanName, mbd, args); //5 执行spring的创建bean实例的流程啦
    return beanInstance;
}
复制代码

接着:

复制代码
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
        Object bean = null;
        if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
            // Make sure bean class is actually resolved at this point.
            if (mbd.hasBeanClass() && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
                //3.1、执行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation回调方法
                bean = applyBeanPostProcessorsBeforeInstantiation(mbd.getBeanClass(), beanName);
                if (bean != null) {
                    //3.2、执行InstantiationAwareBeanPostProcessor的postProcessAfterInitialization回调方法
                    bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
                }
            }
            mbd.beforeInstantiationResolved = (bean != null);
        }
        return bean;
}
复制代码

接着:

复制代码
protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName)
            throws BeansException {
 
        for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) {
            BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next();
            if (beanProcessor instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) beanProcessor;
                Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }
复制代码

接着:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

通过代码我们可以看到BeanPostProcessor,可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑。

十、如何在bean销毁的时候做一些事情?

  有两种方法:A.利用destroy-method配置 B.实现DisposableBean接口。实现DisposableBean接口,在destroy()方法里做一些操作。或者对配置文件加入destroy-method属性指定方法;如果两者同时出现,先执行DisposableBean接口的destroy()方法,然后再执行destroy-method属性指定方法。

十一、Bean的生命周期

  什么时候初始化Bean?当scope=singleton,即默认情况,会在容器初始化时实例化。但我们可以指定Bean节点的lazy-init=”true”来延迟初始化bean,这时候,只有第一次获取bean才会初始化bean,即第一次请求该bean时才初始化。如下配置所示:

<bean id=”xxx” class=”examples.test.OrderServiceBean” lazy-init=”true” />

如果想对所有bean都应用延迟初始化,可以在根节点beans设置default-lazy-init=”true”,如下所示:

<beans default-lazy-init=”true” >

scope=prototype时,也会延迟初始化bean,即第一次请求该bean时才初始化(如调用getBean()方法时)。

  Bean的生命周期,构造器、init方法、获取bean后的操作、destroy方法(ctx.close时执行)。注意:如果bean的scope设为prototype时,当ctx.close时,destroy方法不会被调用。原因:对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言,任何配置好的析构生命周期回调方法都将不会 被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。(让Spring容器释放被prototype作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用。)

在spring容器启动时,refresh方法中,调用invokeBeanFactoryPostProcessors(beanFactory);

 

     在 invokeBeanFactoryPostProcessors 这个方法里,获取实现 BeanFactoryPostProcessor 接口的 bean ,将其排序后,依次调用invokeBeanFactoryPostProcessors。

最终调用了postProcessor.postProcessBeanFactory(beanFactory)方法,该方法会对bean做初始的处理,具体处理方式与子类具体实现有关。

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/tuxedolinux/article/details/79056162
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-05-07 22:39:48
  • 阅读 ( 1134 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢