AOP简介

AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。实际项目中我们通常将面向对象理解
为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在尽量不该原有代码的情况下,在对象运行时动态织入一些扩展功能或控制对象执行)。
AOP 与 OOP 字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些
对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。

实现原理

AOP 可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.如图所示:
AOP实现原理图
其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种:
第一种方式:借助 JDK 官方 API 为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
第二种方式:借助 CGLIB 库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用 final 修饰.

相关术语分析

切面(aspect):横切面对象,一般为一个具体类对象
切入点(pointcut):定义了切入扩展业务逻辑的位置(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点。
通知(Advice):内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(在切面的某个位置上执行的动作(扩展功能))
连接点(joinpoint):程序执行过程中,封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法ProceedingJoinPoint只能被@Around通知使用。连接点与切入点定义如图:
连接点与切入点定义
图解ssm AOP:
aop详解

案例

添加依赖

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>

业务切面对象设计

通过设计切面对象,为目标业务方法做功能增强,关键步骤如下:

第一步:创建注解类型,应用于切入点表达式的定义,关键代码如下:

@Target 用于描述定义的注解能够修饰的对象,@Retention 用于描述定义的注解何时有效。

package com.cy.pj.sys.commen.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog{
    String operation();//在目标业务上使用@RequiredLog时需要为operation赋值
//    String operation() default "";//在目标业务上使用@RequiredLog时不需要为operation赋值
}

第二步:创建切面对象,用于做日志业务增强,关键代码如下:

package com.cy.pj.sys.service.aspect;
import com.cy.pj.sys.commen.annotation.RequiredLog;
import com.cy.pj.sys.dao.SysLogDao;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
/*
在spring aop应用中,基于@Aspect注解描述的
类型为一个切面类型,此类中要封装切入点及通知方法的定义
1.切入点:要切入扩展业务逻辑的一些目标方法集合
2.通知:封装了扩展业务逻辑的一个方法
* 创建切面对象,用于做日志业务增强
* */
@Aspect
@Component
public class SysLogAspect {
    private static final Logger log=
            LoggerFactory.getLogger(SysLogAspect.class);
    /*定义切入点,基于@pointcut注解定义,这里的@annotation为一种切入点表达式
 * 表示由RequiredLog注解描述的方法为一个切入点方法,我们在这样的方法上添加业务扩展
 * */ @Pointcut("@annotation(com.cy.pj.sys.commen.annotation.RequiredLog)")
    public void doLog(){}//此方法只负责承载切入点的定义
 /*
 * @Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展
 * @param jp连接点对象,在此对象封装了要执行的目标方法信息
 * ProceedingJoinPoint此类型的注解只能定义在@Around中
 * 可以通过连接点对象调用目标方法
 * @return目标方法的执行结果
 * @throws Throwable * */ @Around("doLog()")
        //@Around("@annotation(com.cy.pj.sys.commen.annotation.RequiredLog)")
 public Object doAround(ProceedingJoinPoint jp) throws Throwable
 {
            log.info("method start {}", System.currentTimeMillis());
            long t1=System.currentTimeMillis();
            try {
                Object result=jp.proceed();//调用目标方法
 System.out.println(jp.getTarget().getClass().getName());
                log.info("method after {}", System.currentTimeMillis());
                long t2=System.currentTimeMillis();
                doLogInfo(jp,(t2-t1),null);
                return result;
            }catch (Throwable e){
                log.error("exception {}", e.getMessage());
                long t3=System.currentTimeMillis();
                doLogInfo(jp,(t3-t1),e);
                throw e;
            }
        }
//记录用户行为日志
 @Autowired
 private SysLogService sysLogService;
        private void doLogInfo(ProceedingJoinPoint jp,long time,Throwable e) throws NoSuchMethodException, JsonProcessingException {
            //获取行为日志
 String username="tony";//登录用户
 String ip="192.168.100.11";//登录用户的IP借助三方工具类
 //获取用户操作
 //获取方法所在类的字节码对象(目标对象对应的字节码对象)
 Class<?>cls=jp.getTarget().getClass();
            //获取注解描述的方法对象(字节码对象,方法名,参数列表)
 //System.out.println("cls="+cls);
 Signature signature=jp.getSignature();
            //System.out.println("signature="+signature.getClass().getName());//MethodSignatureImpl
 MethodSignature methodSignature= (MethodSignature) signature;
//            Method targetMethod=methodSignature.getMethod();//cglib
 Method targetMethod=//cglib,jdk
 cls.getDeclaredMethod(methodSignature.getName(),
                            methodSignature.getParameterTypes());
//            System.out.println("targetMethod="+targetMethod);
 //获取RequiredLog注解
 RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);
            String operation= requiredLog.operation();
            //String operation=null;
 //获取方法声明信息
//            System.out.println(cls.getName());//com.cy.pj.sys.service.serviceImpl.SysLogServiceImpl
//            System.out.println(targetMethod.getName());//findLogs
 String method=cls.getName()+"."+targetMethod.getName();
            //获取方法执行时传入的实际参数
 String params=new ObjectMapper().writeValueAsString(jp.getArgs());
            //获取状态信息
 Integer status=e==null?1:0;
            String error=e==null?null:e.getMessage();
            //封装用户行为日志
 SysLog sysLog=new SysLog();
            sysLog.setUsername(username);
            sysLog.setIp(ip);
            sysLog.setCreatedTime(new Date());
            sysLog.setOperation(operation);
            sysLog.setParams(params);
            sysLog.setStatus(status);
            sysLog.setError(error);
            sysLog.setTime(time);
            //打印日志
 log.info("user log {}", new ObjectMapper().writeValueAsString(sysLog));
            //将日志写入到数据库
 sysLogService.saveLog(sysLog);
        }
}

第三步:通过注解 RequiredLog 注解描述日志查询或删除业务相关方法,此时这个方法为日志切入点方法,例如:

 @RequiredLog(operation = "公告查询")
 @Override
 public List<SysLog> findLogs(SysLog sysLog) {
     return sysLogDao.selectLogs(sysLog);
 }
 @RequiredLog(operation = "删除日志")
// @RequiredLog
 @Override
 public int deleteById(long... id) {
     return sysLogDao.deleteLogs(id);
 }

Spring AOP技术进阶
通知类型:
@Around (优先级最高的通知,可以在目标方法执行之前,之后灵活进行业务拓展.) !!!!!!最重要ProceedingJoinPoint这个连接点只支持在@Around使用
非@Around通知方法只能使用JoinPoint连接点
@Before (目标方法执行之前调用)通常会在@Before方法中做一些参数数据的过滤或预处理
@AfterReturning (目标方法正常结束时执行)
可以在这样的方法中进行缓存更新操作
@AfterThrowing (目标方法异常结束时执行)
可以在此方法中进行错误日志的记录,可以报警
@After (目标方法结束时执行,正常结束和异常结束它都会执行)通常会在@After描述的方法中进行一些资源释放操作

package com.cy.pj.sys.service.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SysTimeAspect {
    @Pointcut("@annotation(com.cy.pj.sys.commen.annotation.RequiredTime)")
    public void doTime(){}
    //@Before (目标方法执行之前调用)
 @Before("doTime()")
     public void doBefore(){
        System.out.println("@Before");
     }
     //@After (目标方法结束时执行,正常结束和异常结束它都会执行)
 @After("doTime()")
     public void doAfter(){
         System.out.println("@After");
     }
     //@AfterReturning (目标方法正常结束时执行)
 @AfterReturning("doTime()")
     public void doAfterReturning(){
         System.out.println("@AfterReturning");
     }
     //@AfterThrowing (目标方法异常结束时执行)
 @AfterThrowing("doTime()")
     public void doAfterThrowing(){
         System.out.println("@AfterThrowing");
     }
    /**
 *  @Around 描述的方法可以在内部通过连接点对象,手动调用目标方法
 * @param jp 连接点对象,@Around描述的方法只能应用ProceedingJoinPoint。
 * @return
 */ @Around("doTime()")
     public Object doAround(ProceedingJoinPoint jp) throws Throwable {
         System.out.println("--------------------------");
         System.out.println("@Around.before");
         try {
             //proceed方法执行逻辑->本类其它before,其它切面的before,目标方法,当前或其它切面的后续通知方法
 Object result=jp.proceed();
             System.out.println("@Around.AfterReturning");
             return result;
         } catch (Throwable throwable) {
             throwable.printStackTrace();
             System.out.println("@Around.AfterThrowing");
             throw throwable;
         }finally {
             System.out.println("@Around.After");
         }
     }
}

执行结果:

--------------------------
@Around.before
@Before
2021-03-18 12:52:31.095 DEBUG 8768 --- [nio-8081-exec-1] c.c.p.s.dao.SysLogDao.selectLogs_COUNT   : ==>  Preparing: SELECT count(0) FROM sys_logs
2021-03-18 12:52:31.114 DEBUG 8768 --- [nio-8081-exec-1] c.c.p.s.dao.SysLogDao.selectLogs_COUNT   : ==> Parameters: 
2021-03-18 12:52:31.131 DEBUG 8768 --- [nio-8081-exec-1] c.c.p.s.dao.SysLogDao.selectLogs_COUNT   : <==      Total: 1
2021-03-18 12:52:31.132 DEBUG 8768 --- [nio-8081-exec-1] com.cy.pj.sys.dao.SysLogDao.selectLogs   : ==>  Preparing: select id,username,ip,operation,method,params,status,error,time,createdTime from sys_logs order by createdTime desc LIMIT ?
2021-03-18 12:52:31.133 DEBUG 8768 --- [nio-8081-exec-1] com.cy.pj.sys.dao.SysLogDao.selectLogs   : ==> Parameters: 10(Integer)
2021-03-18 12:52:31.137 DEBUG 8768 --- [nio-8081-exec-1] com.cy.pj.sys.dao.SysLogDao.selectLogs   : <==      Total: 10
@AfterReturning
@After
@Around.AfterReturning
@Around.After

applicatiion.properties文件配置AOP代理和线程池

#AOP proxy
spring.aop.proxy-target-class=false
#Spring Thread Pool
spring.task.execution.pool.core-size=8
spring.task.execution.pool.max-size=256
spring.task.execution.pool.keep-alive=60000
spring.task.execution.pool.queue-capacity=128
spring.task.execution.thread-name-prefix=log-thread-

切面的执行顺序

切面的优先级需要借助@Order 注解进行描述,数字越小优先级越高,默认优先级比较低。
最高优先级
最低优先级
spring AOP切入点表达式


fubin
10 声望0 粉丝

下一篇 »
反射