什么是aop

链接: spring中的AOP.

AOP(Aspect Orient Programming),面向切面编程,是面向对象编程 OOP 的一种补充。
在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
AOP的优点就是降低代码之间的耦合,提高代码的复用性。
例如转账功能,在转账代码的前后需要一些非业务方面的处理,权限控制,记录日志,事务的开启与结束,这些代码就可以使用AOP将其切入到转账代码的前后,这样就可以很好地分离业务代码和非业务代码。

spring底层就是采用动态代理模式实现AOP的。采用了两种代理:
JDK 的动态代理,如果被代理了实现了接口,会默认使用jdk的动态代理。底层通过反射方式创建代理类的对象
CGLIB的动态代理,如果类没有实现接口,会使用CGLIB动态代理。底层是对代理类生成的class文件加载进来,通过修改其字节码生成子类来创建代理对象

AOP的术语

(1)目标对象(Target)
目标对象指 将要被增强的对象。即包含主业务逻辑的类的对象。
上例中的UserDaoImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。
当然,不被增强,也就无所谓目标不目标了。

(2)切面(Aspect)
切面泛指非业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知,实际就是对业务逻辑的一种增强。

(3)连接点(JoinPoint)
连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。

(4)切入点(Pointcut)
切入点指切面具体织入的方法。在 UserDaoImpl 类中,若 addUser()被增强,而doOther()不被增强,
则 addUser()为切入点,而 doOther()仅为连接点。 
被标记为 final 的方法是不能作为连接点与切入点的,因为是不能被修改的,不能被增强的。

(5)通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。
上例中的MyInvocationHandler 就可以理解为是一种通知。
换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。
通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。
Advice有下面几种,这里使用常用的AspectJ方式:

前置通知(Before advice):在连接点之前执行,即目标方法执行之前执行。
后置通知(After returning advice):在连接点正常结束之后执行,如果连接点抛出异常,则不执行。
异常通知(After throwing advice):在连接点抛出异常后执行
最终通知(After (finally) advice):在连接点结束之后执行,无论是否抛出异常,都会执行。
环绕通知(Around advice):在连接点之前和之后均执行。



(6)织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke()
方法完成的工作,就可以称为织入。

(7)aop代理(AOP proxy)
spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。

导入pom依赖

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
</dependency>

<!-- fastjson -->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.49</version>
</dependency>

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

自定义aop切点注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义aop切点注解
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Power {

    String value()  default "";

}

自定义aop切面类

import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class PowerAspect {

    /**
     * Controller层切点 注解拦截
     */
    @Pointcut("@annotation(com.zm.aop.Power)")
    public void controllerAspect() {
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作的开始时间
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint){
        Power method = getMethod(joinPoint);
        String shopId = getAnnotationValue(joinPoint, method.value());
        System.out.println("shopId :" + shopId);
        // 获取参数,执行逻辑...
        if("1001".equals(shopId)){
            System.out.println("1001无权限访问");
            return;
        }
        System.out.println("正常访问");
    }

    /**
     * 后置通知 用于拦截Controller层记录用户的操作
     */
    @After("controllerAspect()")
    public void doAfter(JoinPoint joinPoint){

    }

    /**
     * 获取注解中对方法的描述信息
     */
    public static Power getMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod().getAnnotation(Power.class);
    }

    /**
     * 获取注解中传递的动态参数的参数值
     *
     * @param joinPoint
     * @param name
     * @return
     */
    public String getAnnotationValue(JoinPoint joinPoint, String name) {
        String paramName = name;
        // 获取方法中所有的参数
        Map<String, Object> params = getParams(joinPoint);
        // 参数是否是动态的:#{paramName}
        if (paramName.matches("^#\\{\\D*\\}")) {
            // 获取参数名
            paramName = paramName.replace("#{", "").replace("}", "");
            // 是否是复杂的参数类型:对象.参数名
            if (paramName.contains(".")) {
                String[] split = paramName.split("\\.");
                // 获取方法中对象的内容
                Object object = getValue(params, split[0]);
                // 转换为JsonObject
                JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);
                // 获取值
                Object o = jsonObject.get(split[1]);
                return String.valueOf(o);
            }
            // 简单的动态参数直接返回
            return String.valueOf(getValue(params, paramName));
        }
        // 非动态参数直接返回
        return name;
    }

    /**
     * 根据参数名返回对应的值
     *
     * @param map
     * @param paramName
     * @return
     */
    public Object getValue(Map<String, Object> map, String paramName) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getKey().equals(paramName)) {
                return entry.getValue();
            }
        }
        return null;
    }

    /**
     * 获取方法的参数名和值
     *
     * @param joinPoint
     * @return
     */
    public Map<String, Object> getParams(JoinPoint joinPoint) {
        Map<String, Object> params = new HashMap<>(8);
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] names = signature.getParameterNames();
        for (int i = 0; i < args.length; i++) {
            params.put(names[i], args[i]);
        }
        return params;
    }
}

测试参数类

public class TestReq {

    private String shopId;

    public String getShopId() {
        return shopId;
    }

    public void setShopId(String shopId) {
        this.shopId = shopId;
    }


}

模拟接口

import com.zm.aop.TestReq;

public interface TestService {

    String testService2(TestReq testReq);

}

模拟接口实现类

import com.zm.aop.Power;
import com.zm.aop.TestReq;
import com.zm.aop.service.TestService;
import org.springframework.stereotype.Service;

// 业务层无侵入使用
@Service
public class TestServiceImpl implements TestService {

	// 使用对象参数
    @Power("#{testReq.shopId}")
    @Override
    public String testService2(TestReq testReq) {
        return "testService2";
    }

}

TestController

import com.zm.aop.Power;
import com.zm.aop.TestReq;
import com.zm.aop.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.websocket.server.PathParam;

@RestController
public class TestController {

    @Autowired
    private TestService testService;
	
	// 控制层无侵入使用
	// 使用参数
    @Power("#{shopId}")
    @GetMapping("/test1")
    public String test1(@PathParam("shopId")String shopId){
        return shopId;
    }

    @PostMapping("/test2")
    public String test2(@RequestBody TestReq testReq){
        return testService.testService2(testReq);
    }

}

测试

spring 动态给某个类 加 注解 spring注解中动态传值_spring 动态给某个类 加 注解

spring 动态给某个类 加 注解 spring注解中动态传值_java_02

AOP失效注意

在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。
原因是在同一个类之中,方法互相调用,切面无效 ,而不仅仅是事务。这里事务之所以无效,是因为spring的事务是通过aop实现的

不同类调用 A类无事务方法 调用 B类有事务的方法,则事务生效

aop执行顺序及使用场景

链接: 你真的确定Spring AOP的执行顺序吗.

链接: Spring AOP @After,@Around,@Before执行的顺序以及可能遇到的问题.

spring 动态给某个类 加 注解 spring注解中动态传值_System_03

我们将版本改为2.2.5.RELEASE,结果如图:

spring 动态给某个类 加 注解 spring注解中动态传值_aop_04

我们将版本改为2.3.4.RELEASE,结果如图:

spring 动态给某个类 加 注解 spring注解中动态传值_spring_05

注意:从Spring5.2.7开始,Spring AOP不再严格按照AspectJ定义的规则来执行advice,而是根据其类型按照从高到低的优先级进行执行:@Around,@Before ,@After,@AfterReturning,@AfterThrowing。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * aop切点注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RedisLock {
    String key() default "";
}
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class RedisLockAop {

    /**
     * 注解切点
     */
    @Pointcut("@annotation(RedisLock)")
    public void pointcut() {
    }


    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("方法执行前执行......before");
    }

    @AfterReturning("pointcut()")
    public void afterRunning(JoinPoint joinPoint){
        System.out.println("方法执行完执行...afterRunning");
    }

    @After("pointcut()")
    public void after(){
        System.out.println("方法之后执行...after.");
    }

    @Around("@annotation(redisLock)")
    public Object around(ProceedingJoinPoint point, RedisLock redisLock) throws Throwable {
        System.out.println("Around redis锁执行开始========");
//        System.out.println("EXECUTION 调用方法:" + point.getSignature().getName());
//        System.out.println("EXECUTION 目标对象:" + point.getTarget());
//        System.out.println("EXECUTION 获取参数:" + point.getArgs());
        Object proceed = point.proceed();
        System.out.println("Around redis锁执行结束========");
        return proceed;
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing(){
        System.out.println("异常出现之后...afterThrowing");
    }

}

使用场景

日志
事务
利用环绕通知进行 redis加锁和解锁(相同key)
Authentication 权限(特定功能权限限制)
Caching 缓存
Context passing 内容传递(el 取值)
Error handling 错误处理
Debugging 调试(测试时所有接口参数打印)
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence  持久化
Synchronization 同步
Lazy loading 懒加载
Resource pooling 资源池