Spring AOP

一、AOP定义及概念

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。
Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。在不改变原有的逻辑的基础上,增加一些额外的功能。代理也是这个功能,读写分离也能用aop来做。剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

二、专业术语

 

 

相关解释:

(1)横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
(2)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(3)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
(4)Advice(通知):AOP在特定的切入点上执行的增强处理,有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕)
(5)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(6)weave(织入):将切面应用到目标对象并导致代理对象创建的过程
(7)introduction(引入):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
(8)AOP代理(AOP Proxy):AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
(9)目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。

为了帮助更好地理解这些概念,可以进行一个假设(转自https://blog.csdn.net/q982151756/article/details/80513340):

让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.

来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系.
首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
为什么可以这样类比呢?

Joint point : 爪哇的小县城里的百姓: 因为根据定义, Joint point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joint point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.

Pointcut :男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.

Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.

Aspect::Aspect 是 point cut 与 Advice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect.

三、例子

现在我们就来做一个Helloworld项目来说明AOP。

首先创建Maven项目,然后在pom.xml中引入Spring框架的依赖和AOP依赖。

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>

        <!--spring aop + aspectj-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
    </dependencies>

然后可以看一下我们的项目的整个目录

 

 

 在IDEA当中,除了建Maven项目,还需要在resources中新建META-INF包,并新建application.xml,这是作为Spring项目的应用configure文件。

然后首先新建一个Helloworld接口,并声明两个方法。

public interface Hellowworld {
    void printHelloworld();
    void doPoint();

}

然后创建两个实例类:

public class HelloworldImpl1 implements Hellowworld{

    public void printHelloworld() {
        System.out.println("------11111------按下HelloWorld1.printHelloWorld()-----11111111-------");
    }

    public void doPoint() {
        System.out.println("------1111111------打印HelloWorldImpl1-----1111111------");
        return ;
    }

}
public class HelloworldImpl2 implements Hellowworld {

    public void printHelloworld() {
        System.out.println("------222222------按下HelloWorld2.printHelloWorld()------2222222------");
    }

    public void doPoint() {
        System.out.println("-------22222-----打印HelloWorldImpl2------22222-----");
        return ;
    }
}

HelloWorld的两个实现类关注的是业务逻辑,但在此之外还需要其他的功能逻辑等,如打印时间、打印日志等等。这里开始就需要AOP替“HelloWorldImpl”完成!解耦!首先需要一个TimeHandler类。因为一个是切入点前执行、一个是切入点之后执行,所以实现对应接口。
横切关注点,这里是打印时间:

public class TimeHandler implements MethodBeforeAdvice, AfterReturningAdvice {

    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("代理----后----CurrentTime = " + System.currentTimeMillis());
    }

    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("代理----前----CurrentTime = " + System.currentTimeMillis());
    }

}

然后在application.xml里配置AOP:

<?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 id="h1" class="AopTest.HelloworldImpl1" />
    <bean id="h2" class="AopTest.HelloworldImpl2" />

    <!-- 定义通知内容 -->
    <bean id="timeHandler" class="AopTest.TimeHandler" />

    <!-- 定义切入点位置 -->
    <bean id="timePointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*doPoint"></property>
    </bean>

    <!-- 使切入点与通知相关联,完成切面配置 -->
    <bean id="timeHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="timeHandler"></property>
        <property name="pointcut" ref="timePointCut"></property>
    </bean>

    <!-- 设置代理 -->
    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理的对象,有打印时间能力 -->
        <property name="target" ref="h1"></property>
        <!-- 使用切面 -->
        <property name="interceptorNames" value="timeHandlerAdvisor"></property>
        <!-- 代理接口,hw接口 -->
        <property name="proxyInterfaces" value="AopTest.Hellowworld"></property>
    </bean>
    <!-- 设置代理 -->
    <bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理的对象,有打印时间能力 -->
        <property name="target" ref="h2"></property>
        <!-- 使用切面 -->
        <property name="interceptorNames" value="timeHandlerAdvisor"></property>
        <!-- 代理接口,hw接口 -->
        <property name="proxyInterfaces" value="AopTest.Hellowworld"></property>
    </bean>
</beans>

最后运行得到结果:

 

posted @ 2019-10-13 21:11  Zzzwww  阅读(416)  评论(0编辑  收藏  举报
/* 看板娘 */