Spring3:AOP

AOP

AOP(Aspect Oriented Programming),即面向切面编程,可以说是 OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP 引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过 OOP 允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。

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

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

AOP核心概念

1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象

3、连接点(joinpoint)

被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

对连接点进行拦截的定义

5、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

6、目标对象

代理的目标对象

7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程(这个其实才是底层原理)

8、引入(introduction)

在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

Spring对AOP的支持

Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成、管理,其依赖关系也由 IOC 容器负责管理。因此,AOP 代理可以直接使用容器中的其它 bean 实例作为目标,这种关系可由 IOC 容器的依赖注入提供。

Spring 创建代理的规则为:

1、默认使用 Java 动态代理来创建 AOP 代理,这样就可以为任何接口实例创建代理了

2、当需要代理的类不是代理接口的时候,Spring 会切换为使用 CGLIB 代理,也可强制使用CGLIB

AOP 编程其实是很简单的事情,纵观 AOP 编程,程序员只需要参与三个部分:

1、定义普通业务组件

2、定义切入点,一个切入点可能横切多个业务组件

3、定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作(对应上面的就部分是:通知)

所以进行 AOP 编程的关键就是定义切入点和通知,一旦定义了合适的切入点和增强处理,AOP 框架将自动生成 AOP 代理,即:代理对象的方法=增强处理+被代理对象的方法。

下面给出一个 Spring AOP 的 .xml 文件模板,名字叫做 aop.xml,之后的内容都在 aop.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"
    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-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
            
</beans>

基 于 Spring 的 AOP 简单实现

注意一下,在讲解之前,说明一点:使用 Spring AOP,要成功运行起代码,只用 Spring 提供给开发者的 jar 包是不够的,还需要这个两个 jar 包:

1、aopalliance.jar

2、aspectjweaver.jar

开始讲解用 Spring AOP 的 XML 实现方式,先定义一个接口:

public interface HelloWorld
{
    void printHelloWorld();
    void doPrint();
}
class HelloWorld_01 implements HelloWorld
{

    @Override
    public void come()
    {
        System.out.println("HelloWorld_01 来到世界");
    }

    @Override
    public void leave()
    {
        System.out.println("HelloWorld_01  离开世界");
    }
    
}
class HelloWorld_02 implements HelloWorld
{
    
    @Override
    public void come()
    {
        System.out.println("HelloWorld_02 来到世界");
    }
    
    @Override
    public void leave()
    {
        System.out.println("HelloWorld_02  离开世界");
    }
    
}

横切关注点,这里是打印时间:

public class TimeHandler
{
    public void printTime()
    {
        System.out.println("CurrentTime = " + System.currentTimeMillis());
    }
}

有这三个类就可以实现一个简单的 Spring AOP了,看一下 aop.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"
    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-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
        
        <bean id="helloWorld_01" class="com.tkz.aop.HelloWorld_01" />
        <bean id="helloWorld_02" class="com.tkz.aop.HelloWorld_02" />
        <bean id="timeHandler" class="com.tkz.aop.TimeHandler" />
        
        <aop:config>
<aop:aspect id="time" ref="timeHandler"> <aop:pointcut id="addAllMethod" expression="execution(* com.tkz.aop.HelloWorld.*(..))" />   // 这个就是要被增强的地方,也就是“切入点” <aop:before method="printTime" pointcut-ref="addAllMethod" /> // 这两个就是通知:前置、后置(也就是增强内容) <aop:after method="printTime" pointcut-ref="addAllMethod" /> </aop:aspect>    // 整个这个 id 为 "time" 的 aop:aspect 就是一个“横切关注点”,它是一个一个具体的东西,而切面就是所有的“横切关注点”,有点 interface 和 classImpl 的感觉
</aop:config> </beans>

写一个 main 函数调用一下:

public static void main(String[] args)
{
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop.xml");
        
    HelloWorld hw1 = (HelloWorld)applicationContext.getBean("helloWorld_01");
    HelloWorld hw2 = (HelloWorld)applicationContext.getBean("helloWorld_02");
    hw1.come();
    System.out.println();  
    hw1.leave();    // 在执行 come() 和 leave 方法之前都会执行 printTime() 方法
    
    System.out.println();
    hw2.come();
    System.out.println();
    hw2.leave();    // 在执行 come() 和 leave 方法之前都会执行 printTime() 方法
}

看一下运行结果

CurrentTime = 1446129611993
HelloWorld_01 来到世界
CurrentTime = 1446129611993

CurrentTime = 1446129611994
HelloWorld_01 离开世界
CurrentTime = 1446129611994

CurrentTime = 1446129611994
HelloWorld_02 来到世界
CurrentTime = 1446129611994

CurrentTime = 1446129611994
HelloWorld_02 离开世界
CurrentTime = 1446129611994

结果不出所料看到给 HelloWorld 接口的两个实现类的所有方法都加上了代理,代理内容就是打印时间

基于 Spring 的 AOP 使用其他细节

1、增加一个横切关注点,打印日志,Java类为:

public class LogHandler
{
    public void LogBefore()
    {
        System.out.println("Log before method");
    }
    
    public void LogAfter()
    {
        System.out.println("Log after method");
    }
}

aop.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"
    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-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
        
        <bean id="helloWorld_01" class="com.tkz.aop.HelloWorld_01" />
        <bean id="helloWorld_02" class="com.tkz.aop.HelloWorld_02" />
        <bean id="timeHandler" class="com.tkz.aop.TimeHandler" />
        <bean id="logHandler" class="com.tkz.aop.LogHandler" />
        
        <aop:config>
            <aop:aspect id="time" ref="timeHandler">
                <aop:pointcut id="addTime" expression="execution(* com.tkz.aop.HelloWorld.*(..))" />
                <aop:before method="printTime" pointcut-ref="addTime" />
                <aop:after method="printTime" pointcut-ref="addTime" />
            </aop:aspect>
            <aop:aspect id="log" ref="logHandler">
                <aop:pointcut id="printLog" expression="execution(* com.tkz.aop.HelloWorld.*(..))" />
                <aop:before method="LogBefore" pointcut-ref="printLog" />
                <aop:after method="LogAfter" pointcut-ref="printLog" />
            </aop:aspect>
        </aop:config>
</beans>

运行结果

CurrentTime = 1446130273734
Log before method
HelloWorld_01 来到世界
Log after method
CurrentTime = 1446130273735

CurrentTime = 1446130273736
Log before method
HelloWorld_01 离开世界
Log after method
CurrentTime = 1446130273736

CurrentTime = 1446130273736
Log before method
HelloWorld_02 来到世界
Log after method
CurrentTime = 1446130273736

CurrentTime = 1446130273737
Log before method
HelloWorld_02 离开世界
Log after method
CurrentTime = 1446130273737

要想让 logHandler 在 timeHandler 前使用有两个办法:

(1)aspect 里面有一个 order 属性,order 属性的数字就是横切关注点的顺序

(2)把 logHandler 定义在 timeHandler 前面,Spring 默认以 aspect 的定义顺序作为织入顺序

2、我只想织入接口中的某些方法

修改一下 pointcut 的 expression 就好了:

<?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: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-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
        
        <bean id="helloWorld_01" class="com.tkz.aop.helloWorld_01" />
        <bean id="helloWorld_02" class="com.tkz.aop.helloWorld_02" />
        <bean id="timeHandler" class="com.tkz.aop.TimeHandler" />
        <bean id="logHandler" class="com.tkz.aop.LogHandler" />
        
        <aop:config>
            <aop:aspect id="time" ref="timeHandler" order="1">
                <aop:pointcut id="addTime" expression="execution(* com.tkz.aop.HelloWorld.com*(..))" />
                <aop:before method="printTime" pointcut-ref="addTime" />
                <aop:after method="printTime" pointcut-ref="addTime" />
            </aop:aspect>
            <aop:aspect id="log" ref="logHandler" order="2">
                <aop:pointcut id="printLog" expression="execution(* com.tkz.aop.HelloWorld.leav*(..))" />
                <aop:before method="LogBefore" pointcut-ref="printLog" />
                <aop:after method="LogAfter" pointcut-ref="printLog" />
            </aop:aspect>
        </aop:config>
</beans>

运行结果

CurrentTime = 1446130273734
Log before method
HelloWorld_01 来到世界
Log after method
CurrentTime = 1446130273735

CurrentTime = 1446130273736
Log before method
HelloWorld_01 离开世界
Log after method
CurrentTime = 1446130273736

CurrentTime = 1446130273736
Log before method
HelloWorld_02 来到世界
Log after method
CurrentTime = 1446130273736

CurrentTime = 1446130273737
Log before method
HelloWorld_02 离开世界
Log after method
CurrentTime = 1446130273737

表示 timeHandler 只会织入 HelloWorld 接口 com 开头的方法,logHandler 只会织入 HelloWorld 接口 leav 开头的方法

3、强制使用 CGLIB 生成代理

前面说过 Spring 使用 JDK 动态代理或是 CGLIB 生成代理是有规则的,高版本的 Spring 会自动选择是使用 JDK 动态代理还是 CGLIB 生成代理内容,当然我们也可以强制使用 CGLIB 生成代理,那就是 <aop:config> 里面有一个 "proxy-target-class" 属性,这个属性值如果被设置为 true,那么基于类的代理将起作用(也就是 CGLIB),如果 proxy-target-class 被设置为 false 或者这个属性被省略,那么基于接口的代理将起作用(也就是 JDK)。

猜你喜欢

转载自www.cnblogs.com/tkzL/p/8919788.html