AOP简介
AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。实际项目中我们通常将面向对象理解
为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在尽量不该原有代码的情况下,在对象运行时动态织入一些扩展功能或控制对象执行)。
AOP 与 OOP 字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些
对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
实现原理
AOP 可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.如图所示:
其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种:
第一种方式:借助 JDK 官方 API 为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
第二种方式:借助 CGLIB 库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用 final 修饰.
相关术语分析
切面(aspect):横切面对象,一般为一个具体类对象
切入点(pointcut):定义了切入扩展业务逻辑的位置(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点。
通知(Advice):内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(在切面的某个位置上执行的动作(扩展功能))
连接点(joinpoint):程序执行过程中,封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法ProceedingJoinPoint只能被@Around通知使用。连接点与切入点定义如图:
图解ssm 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切入点表达式
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。