Spring AOP实现

什么是 AOP

AOP(Aspect-OrientedProgramming,面向方面编程),可以说是 OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP 则显得无能为力。也就是说,OOP 允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而 AOP 技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP 代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

使用“横切”技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如 Avanade 公司的高级方案构架师 Adam Magee 所说,AOP 的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

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

AOP 使用场景

AOP 用来封装横切关注点,具体可以在下面的场景中使用:

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging  调试

扫描二维码关注公众号,回复: 896478 查看本文章

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence  持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

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 框架一样,在运行时完成织入。

Spring AOP 组件

下面这种类图列出了 Spring 中主要的 AOP 组件

Spring AOP 的简单实现

1. 加入 jar 包

com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar

2. 在 Spring 的配置文件中加入 aop 的命名空间

3. 基于注解的方式来使用 AOP

3.1 在配置文件中配置自动扫描的包

 <context:component-scan base-package="com.xt.beans.aop"></context:component-scan>

3.2 加入使 AspjectJ 注解起作用的配置

 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

为匹配的类自动生成动态代理对象.

4. 编写切面类

4.1 一个一般的 Java 类

4.2 在其中添加要额外实现的功能.

5. 配置切面

5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解

5.2 声明是一个切面: 添加 @Aspect

5.3 声明通知: 即额外加入功能对应的方法.

5.3.1 前置通知
@Before("execution(public int com.xt.beans.aop.ArithmeticCalculator.*(int, int))")

@Before 表示在目标方法执行之前执行
@Before 标记的方法的方法体.
@Before 里面的是切入点表达式:

6. 在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数

7. 注解

@Before: 前置通知,在方法执行之前执行

@After 表示后置通知: 在方法执行之后执行的代码是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候

@AfterRunning: 返回通知,在方法返回结果之后执行 无论连接点是否正常返回还是抛出异常,后置通知都会执行 如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知

@AfterThrowing: 异常通知,在方法抛出异常之后

@Around: 环绕通知,围绕着方法执行

8. 代码实现

github 地址:https://github.com/tsgkim/Spring-AOP-AspectJ

ArithmeticCalculator:

package com.tu.beans.aop;

public interface ArithmeticCalculator {

    int add(int i, int j);

    int addThree(int i, int j, int k);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);
}

ArithmeticCalculatorImpl:

package com.tu.beans.aop;

import org.springframework.stereotype.Component;

@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int addThree(int i, int j, int k) {
        int result = i + j + k;
        return result;
    }

    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }

}

LoggingAspect:

package com.tu.beans.aop;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

//通过添加 @Aspect 注解声明一个 bean 是一个切面!
@Aspect
@Component
public class LoggingAspect {

    @Before("execution(public int com.tu.beans.aop.ArithmeticCalculator.*(int, int))")
    public void beforMethod(JoinPoint joinPoint){
        System.out.println("当前执行的方法:"+joinPoint.getSignature().getName()
                +",执行参数:"+Arrays.toString(joinPoint.getArgs()));
    }

    //拦截所有的类,方法,参数
    @After("execution(* *.*(..))")  
    public void afterMethod(JoinPoint joinPoint){
        System.out.println("日志信息记录值:1.执行方法:"+joinPoint.getSignature().getName()
                +",2.执行参数:"+Arrays.toString(joinPoint.getArgs())
                +",3.执行时间:"+new SimpleDateFormat("yyyy年-MM年-dd日 hh:mm:ss").format(new Date()));
    }

    @Pointcut("execution(* *.*(..))")
    public void pointCut(){}

    //切入点表达式可以通过操作符 && || ! 结合起来
    @AfterReturning("pointCut()")
    public void afterRunningMethod(JoinPoint joinPoint){
        System.out.println("这个是在方法返回结果之后执行");
    }

//    @AfterThrowing("pointCut()")
//    public void afterThrowing(JoinPoint joinPoint,Exception e){
//        System.out.println("一个错误"+e+"在执行方法"+joinPoint.getSignature().getName()+"()时候抛出!");
//    }
}

Test:

package com.tu.beans.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {

        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculatorImpl");

        System.out.println(arithmeticCalculator.getClass().getName());

        int result = arithmeticCalculator.add(11, 12);
        System.out.println("result:" + result);

        result = arithmeticCalculator.div(21, 3);
        System.out.println("result:" + result);

        result = arithmeticCalculator.addThree(15, 3, 9);
        System.out.println("result:" + result);

        /**
         * com.sun.proxy.$Proxy8
            当前执行的方法:add,执行参数:[11, 12]
            日志信息记录值:1.执行方法:add,2.执行参数:[11, 12],3.执行时间:2016年-03年-18日 10:32:20
            这个是在方法返回结果之后执行
            result:23

            当前执行的方法:div,执行参数:[21, 3]
            日志信息记录值:1.执行方法:div,2.执行参数:[21, 3],3.执行时间:2016年-03年-18日 10:32:20
            这个是在方法返回结果之后执行
            result:7

            日志信息记录值:1.执行方法:addThree,2.执行参数:[15, 3, 9],3.执行时间:2016年-03年-18日 10:32:20
            这个是在方法返回结果之后执行
            result:27
         */
    }
}

beans.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:util="http://www.springframework.org/schema/util"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <!-- 自动扫描包 -->
    <context:component-scan base-package="com.tu.beans"></context:component-scan>

    <!-- 自动与AspectJ切面匹配的Bean创建代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

猜你喜欢

转载自blog.csdn.net/woainimax/article/details/78208667