-
AspectJ:Java 社区里最完整最流行的 AOP 框架.
-
在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
使用步骤:
1、加入jar包,使用maven 引入依赖即可
aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
2.spring-mvc.xml配置文件中添加:
1)xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
2) <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
注意了!!!刚开始加在spring.xml中切面没有生效,后来改在spring-mvc.xml中
<beans xmlns="http://www.springframework.org/schema/beans"
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-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启Aspect生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切入点表达式
// 语法结构
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
//权限修饰符--可以不写
//返回类型--* 表示返回值的类型任意
// 指定单类中某个方法
@Pointcut("execution(* com.hsnn.medstgmini.suppur.controller.SuppurController.exportInfo(..))")
// 指定到impl包下面所有方法
execution(* cn.li.service.impl.*.*(String,..))
在Spring中的AOP有6种增强方式,分别是:
1、@Before 前置增强
2、@After 后置增强,使用方法与前置通知相同。注意:后置通知即使方法异常也会成功执行,但是后置通知无法拿到目标方法的返回结果。需要返回通知。
3、@Around 环绕增强
4、@AfterReturning 最终增强,在方法正常之后之后执行的通知,可以拿到目标方法的返回结果。使用返回通知需要注意的是:指定returnning="result",afterReturnningAdvice(JoinPoint joinpoint,Object result)与方法入参位置的对象名称一致,否则会产生异常。
5、@AfterThrowing 异常增强,方法产生异常的时候,可以拿到异常信息。同样需要注意的是:指定throwing="e",与afterThrowingAdvice(JoinPoint joinpoint,Exception e)方法入参位置的异常对象名称一致。
6、@DeclareParents 引入增强
1.基于注解的方式实现springAOP:
1.创建切面类(LoggingAspect)。
@Aspect:声明当前类为切面类。
@Component:交给springIOC容器进行管理。
@Order(number)
:@Order 表示 配置多个切面之间的优先级问题 。 谁的值越小谁的优先级越高 。
具体代码实现如下:》》》》》
//当前类就是一个切面类
//想要一个类成为切面类,1.添加@Component 注释标注 当前类被springIOC容器所管理
//2.@Aspect表示当前类为一个切面类
//@Order 表示 配置多个切面之间的优先级问题 。 谁的值越小谁的优先级越高 。
//@Order(2)
//@Aspect
//@Component
public class LoggingAspect {
// @Before表示前置通知。指的是在特定位置之前,去执行该方法
// 通知 其实是切面类中一个具体的方法
// JoinPoint 表示连接点
/**
* 可以使用AspectJ表达式,对目标方法进行抽象概括。
*
* execution(* cn.li.service.impl.*.*(String,..))
*
* 第一个* 表示匹配所有访问修饰符 以及所有返回值类型的方法 第二个* 表示当前包下所有的类 第三个* 表示所有的方法名称 ..
* 表示匹配任意多个参数。 (String,.. )表示匹配第一个参数为String类型的方法,..表示匹配任意数量任意类型的参数。
* String,String 表示匹配参数为两个字符串的方法。
*/
//@Pointcut("execution(* cn.li.service.impl.*.*(..))")
public void declareRepeatJoinPointExpression(){
}
//@Before("execution(* cn.li.service.impl.*.*(..))")
public void beforeLog(JoinPoint joinpoint) {
// 通过连接点对象可以获得调用目标方法的名称和参数
// 获得方法名称。能拿到你要调用方法的名称
String method = joinpoint.getSignature().getName();
// 获得调用方法时传递的参数
List arguments = Arrays.asList(joinpoint.getArgs());
System.out.println("前置日志调用了方法" + method + "方法,参数是" + arguments);
}
// 注意:后置通知即使方法异常也会成功执行,但是后置通知无法拿到目标方法的返回结果。 需要使用返回通知。。。
//@After("execution(* cn.li.service.impl.*.*(..))")
public void afterLog(JoinPoint joinpoint) {
String method = joinpoint.getSignature().getName();
List arguments = Arrays.asList(joinpoint.getArgs());
System.out.println("后置日志 。");
}
// 返回通知
// 注意:返回通知 ,其实跟后置通知一样 。都是在目标方法执行完之后 才会被执行 。
// returning="result" 名字 要跟参数列表中 Object 对象的名称一致 ,不然产生异常。
//@AfterReturning(value = "execution(* cn.li.service.impl.*.*(..))", returning = "result")
public void testAfterReturning(JoinPoint joinpoint, Object result) {
String method = joinpoint.getSignature().getName();
System.out.println("我是返回通知 。 我在目标方法核心业务执行完才会执行 。" + result);
}
//@AfterThrowing(value="execution(* cn.li.service.impl.*.count(..))",throwing="e")
public void testAfterThrowing(JoinPoint joinpoint,Exception e){
System.out.println("我是异常通知 ,我是在方法产生异常后执行的。"+e);
}
//环绕通知 。 跟动态代理的代码很像。
//@Around("declareRepeatJoinPointExpression()")
// public void around(ProceedingJoinPoint pjp){
// //声明一个Object 对象 用来表示 目标方法的返回值 。
// Object result=null;
// String method=pjp.getSignature().getName();
// try {
// System.out.println("我是前置日志 。。。"+method);
// result = pjp.proceed();//调用proceed() 表示执行被代理类的目标方法。
// System.out.println("我是返回通知"+method+result);
// } catch (Throwable e) {
// //Throwable 所有异常类跟错误类的父类 。Exception Error ...
// // TODO Auto-generated catch block
// e.printStackTrace();
// System.out.println("异常通知,产生异常的时候 会执行catch 里面的代码 。");
// }
// System.out.println("我是后置通知 。。。"+result+method);
// }
}
//@Order(1)
//@Aspect
//@Component
public class CheckAspect {
//@Before("execution(* cn.li.service.impl.*.*(..))")
public void checkBeforeLog(JoinPoint joinpoint){
System.out.println("我是验证切面的前置通知 。");
}
}
2.基于XML文件的方式实现springAOP:
<!--
@Aspect 注解生效 。
让注解生效,切面中的注解生效。
当调用目标方法,跟Aspect中声明的方法相匹配的时候,
AOP框架会自动的为目标方法所在的类创建代理对象。
作用是让注解生效 ,当调用的方法,跟通知中声明的方法一致的时候。AOP框架会自动的为那个方法所在的类生成代理对象,然后在调用目标方法(之前或者之后)把通知中的方法加进去。
-->
<!-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy> -->
<!-- 配置切面 的bean -->
<bean id="checkAspect" class="cn.li.aspect.CheckAspect"></bean>
<bean id="loggingAspect" class="cn.li.aspect.LoggingAspect"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* cn.li.service.impl.*.*(..))" id="pointcut"/>
<aop:aspect ref="checkAspect" order="1">
<aop:before method="checkBeforeLog" pointcut-ref="pointcut"/>
</aop:aspect>
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeLog" pointcut-ref="pointcut"/>
<aop:after method="afterLog" pointcut-ref="pointcut"/>
<aop:after-returning method="testAfterReturning" returning="result" pointcut-ref="pointcut"/>
<aop:after-throwing method="testAfterThrowing" throwing="e" pointcut-ref="pointcut"/>
<!-- <aop:around method="around" pointcut-ref="pointcut"/> -->
</aop:aspect>
</aop:config>
案例1:记录controller 层请求请求地址+请求参数+处理时间
package com.hsnn.medstgmini.aop;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.hsnn.medstgmini.base.sys.model.SysUser;
import com.hsnn.medstgmini.log.constant.LogCons;
import com.hsnn.medstgmini.log.model.TableLog;
import com.hsnn.medstgmini.log.service.ITableLogService;
import com.hsnn.medstgmini.util.SessionUtil;
import org.apache.commons.collections4.MapUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 用户动作记录日志 controller 层记录
*
* @author :chengang
* @date :Created in 2022-12-9 16:38
*/
@Aspect
@Component
public class ActionAspect {
// @Autowired
// private SqlSessionFactory sqlSessionFactory;
@Autowired
private ITableLogService tableLogService;
/**
* 切点设置
*/
// @Pointcut("execution(public * com.hsnn.medstgmini.suppur.controller.SuppurOrderdetailRecentController.exportSuppurOrderdetailRecentDataInfo(..))")
// public void exportSuppurOrderdetailRecentDataInfo() {
// }
@Pointcut("execution(public * com.hsnn.medstgmini.suppur.controller.*.*(..))")
public void all() {
}
@Pointcut("execution(public * com.hsnn.medstgmini.pay.controller.*.*(..))")
public void payAll() {
}
//切点路径
@Pointcut("execution(public * com.hsnn.medstgmini.project.controller.*.*(..))")
public void projectAll() {
}
/**
* 环绕
*
* @param joinpoint
* @return void
* @author
* @date 2022-12-9 16:59
**/
@Around("payAll()|| all()|| projectAll()")
public Object afterLog(ProceedingJoinPoint joinpoint) throws Throwable {
// 获取登陆信息
SysUser sysUser = SessionUtil.getSysUser();
TableLog log = new TableLog();
//2.调用目标方法
// 参数
Map<String, Object> paramMap = new HashMap<>();
String comment = "";
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (!Objects.isNull(sra) && !Objects.isNull(sra.getRequest())) {
HttpServletRequest request = sra.getRequest();
//
Map<String, Object> map = request.getParameterMap();
if (MapUtils.isNotEmpty(map)) {
paramMap.putAll(map);
}
comment = request.getRequestURI();
}
Map<String, Object> map = this.getMethodParams(joinpoint);
if (MapUtils.isNotEmpty(map)) {
paramMap.putAll(map);
}
// 开始时间
Long start = System.currentTimeMillis();
Object proceed = joinpoint.proceed();
//执行方法后
log.setUserId(sysUser.getUserId());
log.setUserName(sysUser.getUserName());
log.setType(LogCons.TypeEnums.SCENE_OPERATION.getKey());
log.setComment(comment);
// 参数
Map<String, Object> reMap = new HashMap<>();
for (String key : paramMap.keySet()) {
if (paramMap.get(key) instanceof String[]) {
String[] cid = (String[]) paramMap.get(key);
if (cid.length > 0) {
reMap.put(key, cid[0]);
}
} else {
reMap.put(key, paramMap.get(key));
}
}
// 方法参数
log.setContextBefore(reMap.toString());
// 执行时间 毫秒
log.setContextAfter(Long.toString(System.currentTimeMillis() - start));
log.setAddTime(new Date());
// 记录日志表
tableLogService.save(log);
return proceed;
}
/**
* 获取请求方法的参数
*
* @param joinPoint
*/
public Map getMethodParams(JoinPoint joinPoint) {
String[] names = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
Map params = new HashMap();
if (ArrayUtils.isEmpty(names)) return params;
Object[] values = joinPoint.getArgs();
for (int i = 0; i < names.length; i++) {
params.put(names[i], values[i]);
}
return params;
}
}
aop不生效原因,需要public方法才能生效。
部分参考:3.1.5 springAOP实现步骤 · java框架 · 看云(感谢大佬的分享)