SpringAOP
AOP是在容器级BeanPostProcessor接口的postProcessAfterInitialization方法中监控每一个Bean初始化完成后,为其生成代理对象,并用代理对象替换原容器的Bean。
与拦截器不同,拦截器通过preHandle返回的boolean值判断是否继续,且拦截器永远早于AOP执行
AOP可以通过异常终止代码,并且@ControllerAdvice注解可以捕获AOP中抛出的异常做统一处理。
失效
https://blog.csdn.net/u012373815/article/details/77345655
https://blog.csdn.net/shanchahua123456/article/details/89766116
保证做到一下几点:被包裹的方法是public,在对象内部的方法中调用该对象的其他使用aop机制的方法,被调用方法的aop注解失效。避免在类内部方法直接调用。
缓存失效用例:
同一个类中的方法直接调用
public class TService{
//在同一个类中的方法,调用 aop注解(@Cacheable 注解也是aop 注解) 的方法,会使aop 注解失效
public User getUser(Integer id){
//此时注解失效,getUserById 方法不会去缓存中查询数据,会直接查询数据库。
return getUserById(id);
}
//使用缓存,查询时先查询缓存,缓存中查询不到时,调用数据库。
@Cacheable(value = "User")
public User getUserById(Integer id){
System.out.println("查询数据库");
return UserDao.getUserById(id);
}
}
解决方案
1 避免避免类内部方法直接调用。最简单彻底,但是要修改代码逻辑
2 注入当前ApplicationContext,注入BeanName,通过Spring上下文获取当前bean,用当前bean调用方法,因为容器中的Bean是已经被替换过的代理对象,所以包含AOP。
@Service
public class TicketService implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext){
applicationContext = applicationContext;
}
//买火车票
public void buyTrainTicket(Ticket ticket){
System.out.println("买到了火车票");
try {
//通过本类Bean
applicationContext.getBean(this.getClass()).sendMessage();
} catch (Exception e) {
logger.warn("发送消息异常");
}
}
@Transactional
public void sendMessage(){
System.out.println("消息存入数据库");
System.out.println("执行发送消息动作");
}
}
3 拿到代理类对象,再调用本类中B方法。(T)AopContext.currentProxy().B()
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
//XML 新增如下语句;先开启cglib代理,开启 exposeProxy = true,暴露代理对象
public class TicketService{
//买火车票
public void buyTrainTicket(Ticket ticket){
System.out.println("买到了火车票");
try {
//通过代理对象去调用sendMessage()方法
(TicketService)AopContext.currentProxy().sendMessage();
} catch (Exception e) {
logger.warn("发送消息异常");
}
}
@Transactional
public void sendMessage(){
System.out.println("消息存入数据库");
System.out.println("执行发送消息动作");
}
}
1 JoinPoint
JoinPoint:提供访问当前被通知方法的目标对象、代理对象、方法参数等数据:
package org.aspectj.lang;
import org.aspectj.lang.reflect.SourceLocation;
public interface JoinPoint {
String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
Object getThis(); //返回AOP代理对象
Object getTarget(); //返回目标对象
Object[] getArgs(); //返回被通知方法参数列表
Signature getSignature():MethodSignature; //返回当前连接点签名
methodSignature.getMethod():Method; //返回被通知方法
SourceLocation getSourceLocation(); //返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分
}
ProceedingJoinPoint:用于环绕通知,使用proceed()方法来执行目标方法:
public interface ProceedingJoinPoint extends JoinPoint {
public Object proceed() throws Throwable;
public Object proceed(Object[] args) throws Throwable; //传入修改后参数,执行目标方法
}
@Around("execution(* com.abc.service.*.many*(..))")
public Object process(ProceedingJoinPoint point) throws Throwable {
//访问目标方法的参数:
Object[] args = point.getArgs();
if (args != null && args.length > 0 && args[0].getClass() == String.class) {
args[0] = "改变后的参数1";
}
//用改变后的参数执行目标方法
Object returnValue = point.proceed(args);
System.out.println("@Around:被织入的目标对象为:" + point.getTarget());
return "原返回值:" + returnValue + ",这是返回结果的后缀";
}
/*
判断参数类型
arg[0].getClass().getTypeName().equals("java.lang.String");
arg[0].getClass().getTypeName().equals("int");
args[0].getClass() == String.class
*/
2 AOP中获取request和response对象
RequestContextHolder是SpringMVC环境中的请求线程上下文,适用于SpringMVC中的任意方法中。
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
3 取得执行目标方法
Signature signature = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature)signature;
Method targetMethod = methodSignature.getMethod();
Method realMethod=proceedingJoinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
4 Round AOP切面包裹整个方法执行过程,且整个过程在同一线程下完成
AOP前面可以做日志、计时、鉴权、验证、后处理等等操作。
@Around("@annotation(sysLog)")
public Object around(ProceedingJoinPoint point, SysLog sysLog) throws Throwable {
String strClassName = point.getTarget().getClass().getName();
String strMethodName = point.getSignature().getName();
log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);
SysLog logVo = SysLogUtils.getSysLog();
logVo.setTitle(sysLog.value());
Long startTime = System.currentTimeMillis();
Object obj = point.proceed(); //方法执行
Long endTime = System.currentTimeMillis();
logVo.setTime(endTime - startTime);
// 发送异步日志事件
applicationContext.publishEvent(new SysLogEvent(logVo));
return obj;
}
5 执行顺序
https://blog.csdn.net/qq_32331073/article/details/80596084#commentBox
在同一个切面类中
在不同切面类中:@Order的值越小越优先。先入后出,后入先出。@Order 默认为最低优先级Integer.MAX_VALUE
事务切面优先级:默认为最低优先级 Integer.MAX_VALUE