整理

https://blog.csdn.net/xlgen157387/article/details/82497594
https://www.cnblogs.com/yeya/p/11169014.html

正文

AOP可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。所谓”切面”,就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性
官方文档地址<

代理模式

什么是代理模式?
代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用

我们可以用举一个电影演员拍戏的例子
一般来说,演员最主要的工作就是演戏,其他的事可以交给他的经纪人去做,例如谈合同,安排档期等等,

而负责这些场外工作的经纪人就相当于Proxy (代理主题角色。负责读具体主题角色的引用,通过真实角色的业务逻辑方法来实现抽象方法,并在前后可以附加自己的操作。)
负责核心业务的演员就是 RealSubject 。(具体主题角色,也就是被代理的对象,是业务逻辑的具体执行者。)
Subject:抽象主题角色。可以是抽象类也可以是接口,是一个最普通的业务类型定义。
Spring AOP 代理模式 - 图1
代理模式分为静态代理和动态代理,静态代理是我们自己创建一个代理类,而动态代理是程序自动帮我们生成一个代理类,可以在程序运行时再生成对象

AOP

主要概念(术语)

  1. 切面(aspect):通常是一个类,里面可以定义切入点和通知
  2. 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
  3. 连接点(joinpoint):

类的哪些方法可以增强,这些方法称为链接点
被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring中连接点指的就是被拦截(增强)到的方法,实际上连接点还可以是字段或者构造器。

  1. 切入点(pointcut):对连接点进行拦截的定义

实际被真正增强的方法

  1. 通知(advice):

实际增强的逻辑部分
所谓通知指的就是指拦截到连接点之后要执行的代码,通知以下五类。

  1. 前置通知:在我们执行目标方法之前运行(@Before)
  2. 后置通知:在我们目标方法运行结束之后 ,不管有没有异常(@After)
  3. 异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
  4. 环绕通知:动态代理, 需要手动执行joinPoint.procced()(其实就是执行我们的目标方法执行之前相当于前置通知, 执行之后就相当于我们后置通知(@Around)
  5. 返回通知:在我们的目标方法正常返回值后运行(@AfterReturning)
    1. 目标对象:代理的目标对象
    2. 织入(weave):将切面应用到目标对象并导致代理对象创建的过程
    3. 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

实现原理

AOP代理:AOP框架创建的对象,代理就是目标对象的加强。

AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

cglib 和 jdk 动态代理

Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。
默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。

Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责
现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。

1. JDK 动态接口代理

一个符合某一接口的实例,生成目标类的代理对象。

JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。
在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;

JDK 动态代理主要涉及到 java.lang.reflect 包中的两个核心类:Proxy 和 InvocationHandler。

  1. 调用newProxyInstance方法

参数1:类加载器 2:增强的方法需要实现的多个接口
3:InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。

Proxy 利用 InvocationHandler 动态创建
JDK动态代理类实现了InvocationHandler接口,重写的invoke方法。
JDK动态代理的基础是反射机制(method.invoke(对象,参数))Proxy.newProxyInstance()
代码示例
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args)
proxy是最终生成的代理对象;
method 是被代理目标实例的某个具体方法;
args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

  1. public class TimeHandler implements InvocationHandler{
  2. private Object target;
  3. public TimeHandler(Object target) {
  4. this.target = target;
  5. }
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. System.out.println("吃饭之前要洗手");
  9. //调用真正的方法
  10. Object retVal = method.invoke(target, args);
  11. System.out.println("吃饭之后要洗碗");
  12. return retVal;
  13. }
  14. }

2. CGLib

CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库
可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多

实现方式

Spring AOP 借助了 ApectJ的语法风格
1.经典的基于代理的AOP
2.@AspectJ注解驱动的切面
3.纯POJO切面
4.注入式AspectJ切面

使用

注解

  1. Spring支持AspectJ的注解式切面编程。

@Aspect 声明一个切面(类上)
@PointCut 声明切点

  1. /**
  2. * 定义切点 所有类上加了Controller注解的
  3. */
  4. @Pointcut("within(@org.springframework.web.bind.annotation.RestController *) || within(@org.springframework" +
  5. ".stereotype.Controller *)")
  6. public void beanAnnotatedWithController() {
  7. }
  8. /**
  9. * 定义切点 所有方法上加了OperationLog注解的
  10. */
  11. @Pointcut(value = "@annotation(operationLog)", argNames = "operationLog")
  12. private void operationLogMethod(OperationLog operationLog) {
  13. }

使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。

  • @After 在方法执行之后执行(方法上)
  • @Before 在方法执行之前执行(方法上)
  • @Around 在方法执行之前与之后执行(方法上)
    1. /**
    2. * 环绕通知
    3. *
    4. * @param joinPoint 切点
    5. * @param operationLog 注解
    6. */
    7. @Around(value = "beanAnnotatedWithController() && operationLogMethod(operationLog)", argNames = "joinPoint,operationLog")
    8. public Object around(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
    9. HttpServletRequest request =
    10. ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    11. String path = request.getRequestURI();
    12. }
    1. // 解析Spring EL表达式,获取到参数值
    2. Object[] objects = joinPoint.getArgs();
    3. EvaluationContext context = new StandardEvaluationContext();
    4. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    5. String[] names = methodSignature.getParameterNames();
    6. for (int i = 0; i < names.length; i++) {
    7. context.setVariable(names[i], objects[i]);
    8. }
    ```java /**
    • 异常通知 *
    • @param joinPoint 切点
    • @param operationLog 自定义注解 */ @AfterThrowing(value = “beanAnnotatedWithController() && operationLogMethod(operationLog)”, throwing = “ex”,
      1. argNames = "joinPoint,operationLog,ex")
      public void afterThrowing(JoinPoint joinPoint, OperationLog operationLog, Exception ex) {

2. 在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)


<a name="TIJC9"></a>
## 应用场景
如权限认证、日志、事务。<br />AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来
<a name="mskZX"></a>
### 1. 切面实现防止重复提交

1. 自定义注解 `@NoRepeatSubmit` 标记所有Controller中的提交请求
1. 通过AOP 对所有标记了 `@NoRepeatSubmit` 的方法拦截
1. 在业务方法执行前,获取当前用户的 token(或者JSessionId)+ 当前请求地址,作为一个唯一 KEY,去获取 Redis 分布式锁(如果此时并发获取,只有一个线程会成功获取锁)
1. 业务方法执行后,释放锁
```java
@Aspect
@Component
public class RepeatSubmitAspect {

    private final static Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);

    @Autowired
    private RedisLock redisLock;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        int lockSeconds = noRepeatSubmit.lockTime();

        HttpServletRequest request = RequestUtils.getRequest();
        Assert.notNull(request, "request can not null");

        // 此处可以用token或者JSessionId
        String token = request.getHeader("Authorization");
        String path = request.getServletPath();
        String key = getKey(token, path);
        String clientId = getClientId();

        boolean isSuccess = redisLock.tryLock(key, clientId, lockSeconds);

        if (isSuccess) {
            LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            // 获取锁成功, 执行进程
            Object result;
            try {
                result = pjp.proceed();

            } finally {
                // 解锁
                redisLock.releaseLock(key, clientId);
                LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);

            }

            return result;

        } else {
            // 获取锁失败,认为是重复提交的请求
            LOGGER.info("tryLock fail, key = [{}]", key);
            return new ResultBean(ResultBean.FAIL, "重复请求,请稍后再试", null);
        }

    }

    private String getKey(String token, String path) {
        return token + path;
    }

    private String getClientId() {
        return UUID.randomUUID().toString();
    }

2. 使用AOP处理请求或记录日志

面向切面的编程,或AOP,是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理。

1.引入依赖

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

2.具体切面代码

/**
 * 日志写入管理
 * zjy
 */
@Aspect
@Component
public class ControllerAspect {

    private final static Logger logger = LoggerFactory.getLogger(ControllerAspect.class);

    @Autowired
    @Qualifier("logServiceImpl")
    private LogService logService;

    @Autowired
    private UserService userService;

    @Pointcut("execution(* com.honghe.managerTool.controller.CommandController.*(..)) "
            + "|| execution(* com.honghe.managerTool.controller.UserController.*(..)) ")
    public void aspect() {//拦截所有类下所有方法
    }

    //配置前置通知,使用在方法aspect()上注册的切入点
    @Before("aspect()")
    public void before(JoinPoint joinPoint) {
        String[] paramNames = ((CodeSignature) joinPoint
                .getSignature()).getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        String userIp = "127.0.0.1";
        String oprationName = joinPoint.getSignature().getName();

        switch (oprationName){
            case "start":
                oprationName=Operation.START.value;
                break;
            case "stop":
                oprationName=Operation.STOP.value;
                break;
            case "restart":
                oprationName=Operation.RESTART.value;
                break;
                default:
                    return;
        }
        if(paramValues.length>0){
            if(paramValues[0] instanceof HttpServletRequest){
                HttpServletRequest request = (HttpServletRequest)paramValues[0];
                userIp = ParamUtil.getIpAddress(request);
            }
        }

        Log log = new Log(oprationName,new Date(),userIp,1);
        logService.saveLog(log);
    }

    //配置后置通知,使用在方法aspect()上注册的切入点
    @After("aspect()")
    public void after(JoinPoint joinPoint){
        logger.info("after out successfully");
    }
}

对http请求解析

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
 * @author: zhaojianyu
 * @create: 2018-10-11 09:11
 **/
@Aspect
@Component
public class HttpAspect {

    private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);

    /**
     * 拦截所有类下所有方法
     */
    @Pointcut("execution(* com.zjy.blog.blog_start.controller.HelloContraller.*(..)) ")
    public void aspect() {
    }

    /**
     * 配置前置通知,使用在方法aspect()上注册的切入点
     */
    @Before("aspect()")
    public void before(JoinPoint joinPoint) {
        logger.info("方法开始执行"+new Date());

        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        //url
        logger.info("url={}",request.getRequestURL());

        logger.info("uri={}",request.getRequestURI());
        //请求方法
        logger.info("method={}",request.getMethod());
        //请求ip地址
        logger.info("ip={}",request.getRemoteAddr());
        //类名  类方法
        logger.info("class_method={}",joinPoint.getSignature().getDeclaringTypeName()+"="+joinPoint.getSignature().getName());
        //参数
        logger.info("args={}",joinPoint.getArgs());
    }

    @After("aspect()")
    public void after() {
        logger.info("方法执行完毕1"+new Date());
    }

    @AfterReturning(returning = "object",pointcut = "aspect()")
    public void afterReturning(Object object) {
        logger.info("方法执行完毕2"+new Date() + object);
    }
}

参考文章:
https://blog.csdn.net/JinXYan/article/details/89302126