首页 > 技术文章 > Spring AOP及动态代理

ruhuanxingyun 2020-08-24 09:13 原文

一、AOP

  1. 定义

    A. AOP(Aspect Oriented Programming):面向切面编程是指在不改变原有的逻辑基础上,增加一些额外的功能,主要是用来解决一些系统层面上的问题,将横切关注点与核心业务逻辑相分离,比如事务、权限、日志等;

    B. OOP(Object Oriented Programming):面向对象编程引入封装、继承、多肽等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。

  2. AOP概念组

    A. 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点;

    B. 切面(Aspect):通常是一个类,里面可以定义切入点和通知;

    C. 连接点(JointPoint):程序执行过程中明确的点,一般是方法的调用,被拦截到的点,因为Spring只支持方法类型的连接点(因为基于动态代理),所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器;

    D. 通知(Advice):AOP在特定的切入点上执行的增强处理

      前置通知(before):在我们执行目标方法之前运行;

      后置通知(after):在我们目标方法运行结束之后 ,不管有没有异常;

      返回通知(afterReturning):在我们的目标方法正常返回值后运行;

      异常通知(afterThrowing):在我们的目标方法出现异常后运行;

      环绕通知(around):在目标方法运行之前和之后执行,环绕通知是最重要的通知类型,需要手动执行joinPoint.procced(),相当于前置通知和后置通知同时作用;

    E. 切入点(Pointcut):就是带有通知的连接点,在程序中主要体现为书写切入点表达式;

    F. 织入(weave):将切面应用到目标对象并导致代理对象创建的过程;

    G. 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段;

    H. AOP代理(AOP Proxy):AOP框架创建的对象,代理就是目标对象的加强,Spring中的AOP代理可以使JDK动态代理(基于接口),也可以是CGLIB代理(基于继承的子类);

    I. 目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。

  3. 使用AOP的几种方式

    A. 基于代理的AOP;

    B. @Aspect注解驱动的切面;

    C. 纯POJO切面,纯粹通过XML <aop:fonfig>标签配置。

  4. @Pointcut切入点详解

    A. 定义一个方法,用于声明切点表达式@Pointcut,该方法一般没有方法体,通知直接使用定义的方法名即可引入当前的切点表达式;

    B. 切点表达式语法,可以使用&&、||、!这三个运算符

    C. AspectJ指示器

      execution():用于匹配是连接点的执行方法;

      @within():匹配使用指定注解的类;

      within:某个类里面;

      @annotation:指定方法所应用的注解。

  5. 示例代码

package com.ruhuanxingyun.aop;

import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 请求日志切面
 * @author: ruphie
 * @date: Create in 2020/10/19 14:14
 * @company: ruhuanxingyun
 */
@Component
@Aspect
@Slf4j
public class LogAspect {

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void pointcut() {}

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Signature signature = proceedingJoinPoint.getSignature();
        // 结果返回值
        Object result = proceedingJoinPoint.proceed();

        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setIp(this.getRemoteAddress(request))
            .setUrl(request.getRequestURL().toString())
            .setHttpMethod(request.getMethod())
            .setClassMethod(String.format("%s.%s()", signature.getDeclaringTypeName(), signature.getName()))
            .setRequestParams(this.buildRequestParam(((MethodSignature) signature).getParameterNames(), proceedingJoinPoint.getArgs()))
            .setResult(result)
            .setTime(System.currentTimeMillis() - start);
        // 日志需一行展示,多行会因高并发下请求导致日志混在一起
        log.info("Request Info:{}", JSONObject.toJSONString(requestInfo));

        return result;
    }

    @Data
    @Accessors(chain = true)
    private class RequestInfo {

        /**
         * IP地址
         */
        private String ip;

        /**
         * 请求路径
         */
        private String url;

        /**
         * 请求方法
         */
        private String httpMethod;

        /**
         * 方法全限名
         */
        private String classMethod;

        /**
         * 请求参数
         */
        private Map<String, Object> requestParams;

        /**
         * 响应结果
         */
        private Object result;

        /**
         * 耗时
         */
        private Long time;

    }

    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0, len = paramNames.length; i < len; i++) {
            if (StringUtils.equals("request", paramNames[i])) {
                continue;
            }

            Object value = paramValues[i];

            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                // 获取文件名
                value = file.getOriginalFilename();
            }

            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }

    private String getRemoteAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip.equals("0:0:0:0:0:0:0:1")) {
            ip = "localhost";
        }
        if (ip.split(",").length > 1) {
            ip = ip.split(",")[0];
        }

        return ip;
    }

}

 

二、代理模式

  1. 作用:为其它对象提供一种代理,以控制对这个对象的访问,用于解决直接访问对象时带来的一些问题。

  2. 分类

    A. 静态代理:目标类和代理类都实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,且在调用真实类方法的前后添加我们需要的功能代码来达到增强的目的;

    B. 动态代理:与静态代理相比,动态代理的代理类不需要程序员自己手动定义,而是在程序运行时动态生成,分为JDK动态代理和CGLIB动态代理。

  3. 静态代理

    A. 优缺点:只能代理一个具体类,如果需要代理一个接口的多个实现的类就需要定义不同的代理类;

    B. 示例代码

package com.ruhuanxingyun.javabasic.proxy;

/**
 * @description: 目标接口
 * @author: ruphie
 * @date: Create in 2020/8/24 22:05
 * @company: ruhuanxingyun
 */
public interface Target {

    void exec();

}
package com.ruhuanxingyun.javabasic.proxy;

/**
 * @description: 目标实现类
 * @author: ruphie
 * @date: Create in 2020/8/24 22:06
 * @company: ruhuanxingyun
 */
public class RealTarget implements Target {

    @Override
    public void exec() {
        System.out.println("real target impl");
    }

}
package com.ruhuanxingyun.javabasic.proxy;

/**
 * @description: 目标静态代理类
 * @author: ruphie
 * @date: Create in 2020/8/24 22:07
 * @company: ruhuanxingyun
 */
public class StaticProxyTarget implements Target {

    private RealTarget target;

    public StaticProxyTarget(RealTarget target) {
        this.target = target;
    }

    @Override
    public void exec() {
        this.before();

        target.exec();

        this.after();
    }

    private void before() {
        System.out.println("handle before");
    }

    private void after() {
        System.out.println("handle after");
    }

}
package com.ruhuanxingyun.javabasic.proxy;

/**
 * @description: 静态代理测试
 * @author: ruphie
 * @date: Create in 2020/8/24 22:10
 * @company: ruhuanxingyun
 */
public class StaticProxyTest {

    public static void main(String[] args) {
        // 目标对象
        RealTarget target = new RealTarget();
        // 代理对象
        StaticProxyTarget proxyTarget = new StaticProxyTarget(target);
        // 执行方法
        proxyTarget.exec();
    }

}

  4. JDK动态代理

    A. 实现步骤

      步骤一:定义一个java.lang.reflect.InvocationHandler接口的实现类,重写invoke方法;

      步骤二:将InvocationHandler对象作为参数传入java.lang.reflect.Proxy的newProxyInstance方法中;

      步骤三:通过调用java.lang.reflect.Proxy的newProxyInstance方法获得动态代理对象;

      步骤四:通过代理对象调用目标方法。

    B. 优缺点:与静态代理一样,目标类需要实现一个代理接口,只能针对实现了接口的类的接口方法进行代理;

    C. 示例代码

package com.ruhuanxingyun.javabasic.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @description: JDK动态代理
 * @author: ruphie
 * @date: Create in 2020/8/24 22:16
 * @company: ruhuanxingyun
 */
public class JdkProxyTargetHandler implements InvocationHandler {

    private Object target;

    public JdkProxyTargetHandler(Class clazz) {
        try {
            this.target = clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.before();

        Object result = method.invoke(target, args);

        this.after();

        System.out.println("proxy class=" + proxy.getClass());
        return result;
    }

    private void before() {
        System.out.println("handle before");
    }

    private void after() {
        System.out.println("handle after");
    }

}
package com.ruhuanxingyun.javabasic.proxy;

import java.lang.reflect.Proxy;

/**
 * @description: JDK动态代理测试
 * @author: ruphie
 * @date: Create in 2020/8/24 22:19
 * @company: ruhuanxingyun
 */
public class JdkProxyTest {

    public static void main(String[] args) {
        // 获取InvocationHandler对象
        JdkProxyTargetHandler handler = new JdkProxyTargetHandler(RealTarget.class);
        // 获取代理类对象
        Target target = (Target) Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(), new Class[]{Target.class}, handler);
        // 调用目标方法
        target.exec();
    }

}

  5. CGLIB动态代理

    A. 实现步骤

      步骤一:定义一个org.springframework.cglib.proxy.MethodInterceptor接口的实现类,重写intercept方法;

      步骤二:获取org.springframework.cglib.proxy.Enhancer类的对象;

      步骤三:分别调用Enhancer对象的setSuperclass和setCallback方法,使用create方法获取代理对象;

      步骤四:通过代理对象调用目标方法。

    B. 优缺点:对指定的业务类生成一个子类,并覆盖其中的业务方法来实现代理,基于继承来实现代理,无法对final类、private方法和static方法实现代理;

    C. 代码示例:后面补充

  6. AOP处理原则   

    A. 如果目标对象的实现类实现了接口,将会采用JDK动态代理来生成AOP代理类;

    B. 如果目标对象的实现类没有实现接口,将会采用CGLIB动态代理来生成AOP代理类。

 

可参考:Spring AOP

    Spring AOP实现原理与CGLIB应用 

 

推荐阅读