Spring AOP 概述
AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程。可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP 可以说也是这种目标的一种实现。我们现在做的一些非业务,如:日志、事务、安全等都会写在业务代码中(也即是说,这些非业务类横切于业务类),但这些代码往往是重复,复制——粘贴式的代码会给程序的维护带来不便,AOP 就实现了把这些业务需求与系统需求分开来做。这种解决的方式也称代理机制。
AOP里面的几个概念
1.切面(Aspect)
官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。“切面”在ApplicationContext 中aop:aspect来配置。连接点(Joinpoint) :程序执行过程中的某一行为,例如,MemberService.get 的调用或者MemberService.delete 抛出异常等行为。
2、通知(Advice)
“切面”对于某个“连接点”所产生的动作。其中,一个“切面”可以包含多个“Advice”。
3、切入点(Pointcut)
匹配连接点的断言,在 AOP 中通知和一个切入点表达式关联。切面中的所有通知所关注的连接点,都由切入点表达式来决定。
4、目标对象(Target Object)
被一个或者多个切面所通知的对象。当然在实际运行时,SpringAOP 采用代理实现,实际 AOP 操作的是 TargetObject 的代理对象。
5、AOP 代理(AOP Proxy)
在 Spring AOP 中有两种代理方式,JDK 动态代理和 CGLib 代理。默认情况下,TargetObject 实现了接口时,则采用 JDK 动态代理,反之,采用 CGLib 代理。强制使用 CGLib 代理需要将 aop:config的 proxy-target-class 属性设为 true。
package com.ruoyi.framework.aspectj;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
@Aspect
public class DemoAspect {
Logger logger = LoggerFactory.getLogger(DemoAspect.class);
/*
*
* Pointcut 定义织入点:
* 有两种方式:①execution 正则表达式方式 ②自定义注解的方式
*
* 正则表达式匹配哪些方法
* 第一个* 代表任意返回类型
* 第二个* 代表任意包名
* 第三个* 代表任意方法名
* (..) 代表任意参数
* *//*
@Pointcut("(execution(* com.ruoyi.*.*(..)))")
public void demoPointCut() {
}*/
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)|| @within(com.ruoyi.common.annotation.DataSource)")
public void dataScopePointCut()
{
}
/**
* 前置通知,在连接点方法前执行
* @param joinPoint
*/
@Before("dataScopePointCut()")
public void doBefore(JoinPoint joinPoint){
// 获得方法签名
Signature signature = joinPoint.getSignature();
// 获得注解,后就可以取到注解里自定义的参数
MethodSignature methodSignature = (MethodSignature) signature;
//方法1
Method method = methodSignature.getMethod();
DataScope dataScope1 = method.getAnnotation(DataScope.class);
//方法2
// DataScope dataScope2 = AnnotationUtils.getAnnotation(methodSignature.getDeclaringType(), DataScope.class);
// 获得方法参数
Object[] args = joinPoint.getArgs();
Object arg = args[0];// 方法第一个参数
if (StringUtils.isNotNull(arg)&&arg instanceof BaseEntity){
// 执行后续业务
}
}
/**
* 环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法
* @param proceedingJoinPoint
*/
@Around("dataScopePointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
logger.info("方法前逻辑......");
try {
return proceedingJoinPoint.proceed();
}finally {
logger.info("方法后逻辑......");
}
}
/**
* 后置通知,在连接点方法后调用
* @param joinPoint
*/
@After("dataScopePointCut()")
public void after(JoinPoint joinPoint){
logger.info("方法后调用.....");
}
/**
*返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常
* @param joinPoint
* @param jsonResult
*/
@AfterReturning(value = "dataScopePointCut()",returning = "jsonResult")
public void afterReturning(JoinPoint joinPoint,Object jsonResult){
logger.info("返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常");
}
/**
* 异常通知,当连接点方法异常时调用
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "dataScopePointCut()",throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint,Exception e){
logger.info("异常通知,当连接点方法异常时调用");
}
}
package com.ruoyi.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.ruoyi.common.enums.DataSourceType;
/**
* 自定义多数据源切换注解
*
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*
* @author ruoyi
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}