spring的AOP

时间:2022-07-25
本文章向大家介绍spring的AOP,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

AOP简介

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

关于OOP参考文章:

https://oakland.github.io/2017/03/30/%E5%86%8D%E7%9C%8BOOP%EF%BC%8C%E5%88%B0%E5%BA%95%E4%BB%80%E4%B9%88%E6%98%AFOOP/

个人理解:AOP如某些大楼,门口站的保安,这些保安有些一天24小时都在岗(预编译)、有些按时间点站在那(运行期间动态代理)、有些是根据某些大领导来才来站站样子、但是其实这些保安针对一大楼来说,不属于门,也不属于这栋大楼、可有可无,大楼不会因为缺少了保安而倒塌了,保安只是提供一些必要的环节(前置通知、后置通知、环绕通知等)。

AOP解决了什么问题?

每一种思想或每一种技术的存活甚至说流程,要么解决一个业界的问题,要用提出的这个新理念非常给力,但最终好用的思想或技术都是为解决某问题而存活的,AOP也是一样,主要解决的是:

解耦:对象与对象之间建立松耦合,而不会嵌入到代码中去;

复用性高、拓展性高:由于建立松耦合所以代码复用性及拓展性变高,间接反映系统的可维护性也增加了;

相关概念

Aspect

切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;

Join point

连接点,也就是可以进行横向切入的位置;

Advice

通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );

Pointcut

切点,符合切点表达式的连接点,也就是真正被切入的地方;

方法式例

什么是切面?

切面?当我第一次读到的时候还以为是切面条,书上的这样定义的:切面是通知和切点的结合。然后就没了....我只能说真的...不懂..

而我的理解是:将系统的某一个功能面,比如日志系统,指一这个面,然后将公共的功能抽出来统一管理,调用。也就是从日志系统切出一些公共功能组成模块。

什么是静态切面?什么是动态切面?

静态切面指的是在编译期(生成.class)就确定增强是否需要织入目标类的连接点上;

动态切面指的是在运行期间根据方法入参的值来判断是否需要织入目标类的边接点上;

但不管是静态切面还是动态切面都是通过动态代理技术实现,其实动态代码无法就两种jdk代理或CGlib代理;

实现代码

  1. 基于xml的配置

项目结构

/**
 * 返回工具类
 * @param <T>
 */
public class DataResponse<T> implements Serializable {
    /** 默认为0*/
    public static final Integer SUCCESS = 0;
    /** 错误标识*/
    public static final Integer FAIL = 1;
    /** 编码*/
    private int code;
    /** 返回消息*/
    private String msg;
    /** 数据*/
    private T data;
    /** 总条数*/
    private int total;

    public DataResponse() {
        this.code = SUCCESS;
    }

    public DataResponse(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public DataResponse(Integer code, String msg, Boolean isNeedTry) {
        this.code = code;
        this.msg = msg;
    }

    public DataResponse(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public DataResponse(Integer code, String msg, T data, Boolean isNeedTry) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static DataResponse BuildFailResponse() {
        return BuildFailResponse((String)null);
    }

    public static DataResponse BuildFailResponse(String msg) {
        return BuildFailResponse(msg, (Object)null);
    }

    public static <T> DataResponse BuildFailResponse(String msg, T data) {
        DataResponse rtv = new DataResponse();
        rtv.setCode(FAIL);
        rtv.setMsg(msg);
        rtv.setData(data);
        return rtv;
    }

    public static DataResponse BuildSuccessResponse() {
        return BuildSuccessResponse((Object)null);
    }

    public static <T> DataResponse BuildSuccessResponse(T data) {
        DataResponse rtv = new DataResponse();
        rtv.setCode(SUCCESS);
        rtv.setData(data);
        return rtv;
    }

    public Integer getCode() {
        return this.code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public T getData() {
        return this.data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMsg() {
        return this.msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public int getTotal() {
        return total;
    }
}
/**
 * @Auther: csh
 * @Date: 2020/7/14 16:26
 * @Description:用户服务
 */
public interface UserService {
    /**
     *
     * 功能描述:登陆
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 16:29
     */
    DataResponse<Boolean> login(String username, String password);
    /**
     *
     * 功能描述: 注册
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 16:29
     */
    DataResponse<Boolean> register(String username,String password);
}
/**
 * @Auther: csh
 * @Date: 2020/7/14 16:30
 * @Description:用户服务实现
 */
@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public DataResponse<Boolean> login(String username, String password) {
        System.out.println("login:用户账号"+username+"密码:"+password);
        if(StringUtils.isEmpty(username)|| StringUtils.isEmpty(password)){
            throw  new NullPointerException("账号或密码为空!");
        }
        return DataResponse.BuildSuccessResponse(true);
    }

    @Override
    public DataResponse <Boolean> register(String username, String password) {
        System.out.println("register:用户账号"+username+"密码:"+password);
        return DataResponse.BuildSuccessResponse(true);
    }
}
/**
 * @Auther: csh
 * @Date: 2020/7/14 16:33
 * @Description:拦截器
 */
public class LogInterceptor {
    /**
     *
     * 功能描述:前置通知
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 16:33
     */
    public void beforeExecute(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List <Object> objects = Arrays.asList(joinPoint.getArgs());
        System.out.println(this.getClass().getSimpleName()+"前置拦截");
    }


    /**
     * 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果
     * @param joinPoint
     */
    public void afterExecute(JoinPoint joinPoint){

        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法名称:"+methodName+this.getClass().getSimpleName()+" after execute");
    }

    /**
     *
     * 功能描述:后置返回通知:在方法正常执行后执行的代码,可以获取到方法的返回值
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 16:35
     */
    public void afterReturning(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法名称"+methodName+this.getClass().getSimpleName()+" 后置拦截, 返回值:"+result);
    }
    /**
     *
     * 功能描述:后置异常通知:在方法抛出异常之后执行,可以访问到异常信息,且可以指定出现特定异常信息时执行代码
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 16:38
     */
    public void afterThrowing(JoinPoint joinPoint,Exception exception){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常方法:"+methodName+this.getClass().getSimpleName()+"后置异常通知,异常:"+ exception);
    }

    /**
     *
     * 功能描述: 环绕通知, 围绕着方法执行
     *
     * @param:
     * @return: 
     * @auther: csh
     * @date: 2020/7/14 16:39
     */
    public Object around(ProceedingJoinPoint joinPoint){

        String methodName = joinPoint.getSignature().getName();
        System.out.println("环绕方法:"+methodName+this.getClass().getSimpleName()+" 环绕拦截开始");

        Object result = null;
        try {
            //执行目标方法
            result = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return result;
    }
}

spring-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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 自动扫描的包 -->
    <context:component-scan base-package="com.hong.spring.aop.service">
    </context:component-scan>

    <!-- 启用AspectJ自动代理 -->
    <aop:aspectj-autoproxy />

    <!-- 配置 bean -->
    <bean id="userService"
          class="com.hong.spring.aop.service.impl.UserServiceImpl"></bean>

    <!-- 配置切面 -->
    <bean id="logInterceptor"
          class="com.hong.spring.aop.interceptor.LogInterceptor"></bean>

    <!-- aop配置 -->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut expression="execution(* com.hong.spring.aop.service.UserService.*(..))"
                      id="pointcut"/>
        <!-- 配置切面及通知 -->
        <aop:aspect ref="logInterceptor" order="1">
            <aop:before method="beforeExecute" pointcut-ref="pointcut"/>
            <aop:after method="afterExecute" pointcut-ref="pointcut"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="exception"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
            <aop:around method="around" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

</beans>
/**
 * @Auther: csh
 * @Date: 2020/7/14 16:46
 * @Description:基于xml的aop测试
 */
public class SpringAopXmlApp {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:aop/spring-aop.xml");
        UserService userService = ctx.getBean(UserService.class);
        userService.login("hong","123456");
      //  userService.register("hong2","123456");
        ctx.close();
    }
}

结果

LogInterceptor前置拦截
七月 14, 2020 4:51:33 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose
环绕方法:loginLogInterceptor 环绕拦截开始
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@31221be2: startup date [Tue Jul 14 16:51:32 CST 2020]; root of context hierarchy
login:用户账号hong密码:123456
方法名称loginLogInterceptor 后置拦截, 返回值:com.hong.spring.aop.common.DataResponse@7e07db1f
方法名称:loginLogInterceptor after execute

基于注解

结构图

实现代码

/**
 * @Auther: csh
 * @Date: 2020/7/14 17:01
 * @Description:订单接口服务
 */
public interface OrderService {
    /**
     *
     * 功能描述:支付
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 17:02
     */
    DataResponse<Boolean> pay(String orderId,int payMoney);
}
/**
 * @Auther: csh
 * @Date: 2020/7/14 17:01
 * @Description:订单实现
 */
@Service
public class OrderServiceImpl implements OrderService {
    @Override
    public DataResponse<Boolean> pay(String orderId,int payMoney) {
        if(payMoney==0||payMoney<=0){
            throw  new IllegalArgumentException("支付失败,金额有误!");
        }
        System.out.println("支付订单号:"+orderId+"支付金额:"+payMoney);
        return DataResponse.BuildSuccessResponse();
    }
}
/**
 * @Auther: csh
 * @Date: 2020/7/14 17:05
 * @Description:切面类
 */
@Component
@Aspect
@Order(1)
public class TransferLogAdvice {
    /**
     *
     * 功能描述:切入点
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 18:25
     */
    @Pointcut("execution(* com.hong.spring.aop.service.impl.OrderServiceImpl.*(..))")
    public void pointcut1(){

    }
    /**
     *
     * 功能描述:切入点
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 18:26
     */
    @Pointcut("execution(* com.hong.spring.aop.service.impl.*ServiceImpl.*(..))")
    public void myPointcut(){

    }
    /**
     *
     * 功能描述:前置通知:在方法前面执行
     *
     * @param: 
     * @return: 
     * @auther: csh
     * @date: 2020/7/14 18:30
     */
    @Before(value = "pointcut1() || myPointcut()")
    public void beforeExecute(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List <Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(this.getClass().getSimpleName()+"前置通知方法"+methodName+"参数"+args);
    }
    /**
     *
     * 功能描述:后置通知:在方法后执行
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/7/14 18:33
     */
    @After(value = "pointcut1()")
    public void afterExecute(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List <Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println(this.getClass().getSimpleName()+"后置通知方法"+methodName+"参数"+args);
    }

    /**
     * 后置返回通知:在方法正常执行后执行的代码,可以获取到方法的返回值
     * @param joinPoint
     */
    @AfterReturning(value = "pointcut1()",
            returning="result")
    public void afterReturning(JoinPoint joinPoint, Object result){

        String methodName = joinPoint.getSignature().getName();
        System.out.println(this.getClass().getSimpleName()+ " 后置返回通知方法名称:"+methodName+" 返回值:"+result);
    }

    /**
     * 后置异常通知:在方法抛出异常之后执行,可以访问到异常信息,且可以指定出现特定异常信息时执行代码
     * @param joinPoint
     */
    @AfterThrowing(value = "pointcut1()",
            throwing="exception")
    public void afterThrowing(JoinPoint joinPoint, Exception exception){

        String methodName = joinPoint.getSignature().getName();
        System.out.println(this.getClass().getSimpleName()+ " 后置异常通知方法名称:"+methodName+" 异常:"+exception);
    }

    /**
     * 环绕通知, 围绕着方法执行
     */
    @Around(value = "pointcut1()")
    public Object around(ProceedingJoinPoint joinPoint){

        String methodName = joinPoint.getSignature().getName();
        System.out.println(this.getClass().getSimpleName()+ " 环绕通知:"+methodName+" 环绕通知开始");

        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println(this.getClass().getSimpleName()+ " 环绕通知:"+methodName+" 环绕通知结束");
        return result;
    }
}

spring-aop-annotation.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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 自动扫描的包 -->
    <context:component-scan base-package="com.hong.spring.aop">
    </context:component-scan>

    <!-- 启用AspectJ自动代理 -->
    <aop:aspectj-autoproxy />

</beans>
/**
 * @Auther: csh
 * @Date: 2020/7/14 16:46
 * @Description:基于注解的aop测试
 */
public class SpringAopAnnotationApp {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:aop/spring-aop-annotation.xml");
        OrderService orderService = ctx.getBean(OrderService.class);
        orderService.pay("xxxxx1",0);


        System.out.println("==================================");
        orderService.pay("xxxxx2",100);
        ctx.close();
    }
}

结果

TransferLogAdvice 环绕通知:pay 环绕通知开始
TransferLogAdvice前置通知方法pay参数[xxxxx1, 0]
TransferLogAdvice 环绕通知:pay 环绕通知结束
TransferLogAdvice后置通知方法pay参数[xxxxx1, 0]
TransferLogAdvice 后置返回通知方法名称:pay 返回值:null
==================================
TransferLogAdvice 环绕通知:pay 环绕通知开始
TransferLogAdvice前置通知方法pay参数[xxxxx2, 100]
java.lang.IllegalArgumentException: 支付失败,金额有误!
  at com.hong.spring.aop.service.impl.OrderServiceImpl.pay(OrderServiceImpl.java:17)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
  at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
  at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
  at com.hong.spring.aop.interceptor.TransferLogAdvice.around(TransferLogAdvice.java:113)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
  at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)
  at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
  at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
  at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
  at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
  at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
  at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
  at com.sun.proxy.$Proxy14.pay(Unknown Source)
  at com.hong.spring.aop.SpringAopAnnotationApp.main(SpringAopAnnotationApp.java:16)
支付订单号:xxxxx2支付金额:100
TransferLogAdvice 环绕通知:pay 环绕通知结束
TransferLogAdvice后置通知方法pay参数[xxxxx2, 100]
TransferLogAdvice 后置返回通知方法名称:pay 返回值:com.hong.spring.aop.common.DataResponse@a514af7

最后

AOP在运行时织入效率太低,而且只能针对方法进行AOP,无法针对构造函数、字段进行AOP。建议在编译成class时就织入。其原理就是使用JDK或CGLIB动态代理进行实现,而JDK所创建的代理对象的性能比CGLIB低10倍,反过来JDK创建的花费时间比CGLIB快8倍左右。AOP这个思想其实就是解耦、切入、提高代码重用性,并且其原理就是反射衍生而来的代理模式可参考:https://blog.csdn.net/qq_16498553/article/details/106589260

本文涉及AOP内容还是有点少,后续再统一完整补充。

参考文章:

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-api

https://my.oschina.net/huangyong/blog/161338

https://www.jianshu.com/p/007bd6e1ba1b

https://www.cnblogs.com/jingzhishen/p/4980551.html