头图

1. 概述

从实现的角度来说,代理分为基于类的代理和基于接口的代理,基于接口的代理有 静态代理动态代理,而基于类的代理需要依赖第三方库,比如 cglibcglib的代理在运行时动态生成字节码文件来实现代理。

2. 静态代理

在编译期间就已经实现代理

2.1 实现静态代理的必要条件

  1. 基于接口或者抽象类
  2. 代理类实现接口或者继承抽象类,通过构造函数传入目标对象
  3. 重写方法,通过目标对象去执行业务逻辑
  4. 而代理对象去执行与业务不想关的逻辑

2.2 示例代码

public interface UserService {
    /**
     * 添加
     * @param email 邮箱
     * @return 受影响的行数
     */
    int add(String email);
}
public class UserServiceImpl implements UserService {
    @Override
    public int add(String email) {
        System.out.println(String.format("email:%s", email));
        return 1;
    }
}
public class UserServiceProxyImpl implements UserService {
    private final UserService targetObject;

    public UserServiceProxyImpl(UserService targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public int add(String email) {
        System.out.println("begin transaction");
        int affectedRow = targetObject.add(email);
        System.out.println("commit transaction");
        System.out.println(String.format("affected row:%s", affectedRow));
        return affectedRow;
    }
}

2.4 静态代理的缺点

  1. 只能代理一个实现类或接口,无法做到代理多个类,这样照成了代理类的作用范围的局限性
  2. 对于不需要代理的接口方法也需要实现,造成的代码的冗余,扩展难的问题
  3. 依赖于接口

3. 动态代理

在运行期间动态生成字节码文件

3.1 示例代码

public class ServiceProxy implements InvocationHandler {
    private final Object targetObject;
    private ClassLoader classLoader;
    private Class<?>[] interfaces;

    public ServiceProxy(Object targetObject) {
        this.targetObject = targetObject;
        this.classLoader = targetObject.getClass().getClassLoader();
        this.interfaces = targetObject.getClass().getInterfaces();
    }

    public Object getProxyObject() {
        return Proxy.newProxyInstance(classLoader, interfaces, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(String.format("%s.%s", targetObject.getClass().getName(), method.getName()));
        System.out.println("begin transaction");
        if (args != null) {
            System.out.println(String.format("params:%s", Arrays.toString(args)));
        }
        Object retVal = method.invoke(targetObject, args);
        System.out.println("commit transaction");
        System.out.println(String.format("affected row:%s", retVal));
        return retVal;
    }
}
public interface UserService {
    /**
     * 添加
     * @param email 邮箱
     * @return 受影响的行数
     */
    int add(String email);
}
public class UserServiceImpl implements UserService {
    @Override
    public int add(String email) {
        return 1;
    }
}

3.2 与静态代理的区别

  1. 静态代理在编译期就已经完成,而动态代理在运行时完成
  2. 静态代理只能代理一个类,而动态代理可以代理多个类
  3. 都依赖与接口
  4. 静态代理扩展难以及难以维护,而动态代理代理了接口中的所有的方法,达到了通用性。
  5. 由于动态代理代理了接口中的所有方法,如果目标对象需要扩展接口方法且不依赖代理对象就需要关注代理的实现细节。

4. CgLig 代理

cglib全称Code Generation Libary,即 代码生成库,能能够在运行时生成子类对象从而达到对目标对象扩展的功能。

4.1 优点

  1. 无论是静态代理还是动态代理都依赖与接口,而cglib可以代理一个类,这样就达到了代理类的无侵入性。
  2. 在运行时动态生成字节码文件

4.2 代码示例

要是用cglib代理,我们需要引入 cglib的依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>
public class ServiceProxyFactory implements MethodInterceptor {
    private final Object targetObject;

    public ServiceProxyFactory(Object targetObject) {
        this.targetObject = targetObject;
    }

    public Object createProxyObject() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetObject.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//        System.out.println(String.format("obj:%s",obj));
        if (args != null) {
            System.out.println(String.format("params:%s", Arrays.asList(args)));
        }
        System.out.println(String.format("method proxy:%s",proxy));
        System.out.println(String.format("method signature:%s",proxy.getSignature()));
        System.out.println(String.format("method super index:%s",proxy.getSuperIndex()));
        System.out.println(String.format("method super name:%s",proxy.getSuperName()));
        System.out.println("begin transaction");
        Object affectedRow = method.invoke(targetObject, args);
        System.out.println("commit transaction");
        System.out.println(String.format("affected row:%s", affectedRow));
        return affectedRow;
    }
}
public class UserService {
    public int add(String email) {
        System.out.println(String.format("email:%s", email));
        return 1;
    }
}
public class Main {
    public static void main(String[] args) {
        UserService userService = new UserService();
        System.out.println(userService);
        ServiceProxyFactory serviceProxyFactory = new ServiceProxyFactory(userService);
        UserService proxyObject = (UserService) serviceProxyFactory.createProxyObject();
        System.out.println(proxyObject);
        proxyObject.add("shaw@gmail.com");
    }
}

5. 小结

  1. 静态代理实现比较简单,通过代理对象对目标对象进行包装,就可以实现增强功能,但静态代理只能对一个类进行代理,如果代理目标对象过多则会产生很多代理类。
  2. JDK代理也就是动态代理,代理类需要实现InvocationHandler接口
  3. 静态代理在编译生成字节码文件,直接使用,效率高。
  4. 动态代理和cglib代理基于字节码,在运行时生成字节码文件,效率比静态代理略低。
  5. 静态代理和动态代理都是基于接口的,而cglib可以基于类进行代理
  6. cglib代理在运行时生成代理对象的子类,重写该子类的方法来实现代理,因此代理对象被代理的方法不能被final修饰,代理类需要实现MethodInterceptor

6. AOP

Aspect-oriented programming,即面向切面编程,将代码进行模块划分,降低耦合,分离关注点,减少对业务代码的侵入性。

在面向对象中,我们有三大核心特征,分别是:

  1. 封装
  2. 继承
  3. 多态

这三种特性中使用的最多的就是多态,加上由于使用组合代替继承降低对父类的依赖不再关心实现细节,而实现多态更多的抽象出一个顶级接口,因此我们从面向对象的封装特称抽象出"面向接口编程"这个概念。而AOP是对面向对象的补充,面向对象以"类"作为基本单元,根据不同的业务场景去将我们的类进行模块化,我们的AOP模块化的关键是切面/AspectAspect不再关心具体的类型,通过代理技术对目标对象进行增强。

其中代理又分为基于classinterface的代理,基于class的代理我们一般指的是aspectJ,而基于interface的代理则是静态代理和动态代理。

6.1 为什么要使用AOP

6.1.2 代码无侵入性

在不改变类结构的情况下,动态的生成字节码对类进行扩展。

6.1 AOP相关概念

6.1.1 Aspect

首先第一个就是我们的aspectaspect类似于我们的类,在很多工具类中,它表达的更多其实是通用函数的一个容器,用于保存与这个函数容器相关的方法,而aspect就是pointCutJoinPointAdvice的容器。

一个aspect可以包含多个pointCutAdvice,但是只能包含一个JoinPoint,因此关系是一对一对多对多。

6.1.2 PointCut

Spring AOP中我们只能对方法进行拦截,但并不是所有方法都需要我们进行一个增强,因此PointCut就是Spring AOP给我们提供的对方法进行过滤的一个功能,在Spring官方文档中把它称为 切入点

6.1.3 JoinPoint
6.1.4 Advice

在拦截了需要代理之后,我们可以就可以对方法进行增强,Spring给我们提供以下执行增强的动作:

  1. around
  2. before
  3. after-returning
  4. after-throwing
  5. (finally)after

beforeafter-returningafter-throwing这都很好理解,分别是方法执行之前的动作、方法正常返回的动作、方法异常退出的动作。而finally-aftertry-catch-finally如果把after-retruning比作try,把after-throwing比作catch,那么after就是finally

我们可以简单对比try-catch-finally的执行流程来理解after-retruningafter-thrwoingfinally-after

  1. try块中没有抛出异常,那么不会走catch
  2. try中抛出异常且被catch捕获,如果方法抛出那么会终止try后续的代码执行catch
  3. 除非JVM虚拟机退出,否者一定执行finally

Spring-AOP执行的动作中:

  1. 如果方法正常执行没有抛出异常,那么不会执行after-throwing
  2. 如果方法执行过程中抛出了异常,那么会执行after-throwing
  3. 无论有没有抛出异常都会执行finally-after

try-catch-finaly中,我们可以根据finally这个特性来执行一些资源释放的操作,比如数据库的关闭、IO的关闭。同样的,在after-finlaly中我们可以执行一些资源释放操作,比如释放锁。

7. Spring 对 AOP的整合

要使用apring-aop我们需要引入SpringAspectJ的支持:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.framework.version}</version>
</dependency>

7.1 启用AspectJ的支持

7.1.1 Java Configuration
@EnableAspectJAutoProxy
public class AspecJConfiguration{
    
}
7.1.2 XML Configuration
<aop:aspectj-autoproxy/>

7.2 定义 Aspect

7.2.1 Java Configuration
import org.aspect.lang.annotaion.Aspect;

@Aspect
public class RepositoryPerformance {
        
}

7.3 定义 PointCut

https://blog.csdn.net/qq52509...

7.3.1 Java Configuration
@PointCut("execution* com.mobile.train.diga.mapper..*(..))")
public void repositoryOps(){
    
}

7.4 定义通知

7.4.1 Before 通知

在方法执行之前的通知

@Before("repositoryOps()")
public void before() {
    System.out.println("BEFORE CALLING");
}
7.4.2 After Returning 通知

在方法正常执行返回之后执行

@AfterReturning(value = "repositoryOps()", returning = "retVal")
public void afterReturning(Object retVal) {
    System.out.println(String.format("retVal:%s", retVal));
}
7.4.3 After Throwing 通知

在方法异常退出之后执行

@AfterThrowing(value = "repositoryOps()", throwing = "throwable")
public void afterThrowing(Throwable throwable) {
    throwable.printStackTrace(System.err);
}
7.4.4 After Finally 通知

在方法执行完成退出时候,可以利用这个通知释放资源,例如,锁

@After(value = "repositoryOps()")
public void releaseLock() {
    System.out.println("DO RELEASE LOCK!");
}
7.4.5 around 通知

在方法执行过程中进行包装环绕运行,可以利用环绕通知进行行性能统计

@Around(value = "repositoryOps()")
public Object doBasicProfiling(ProceedingJoinPoint joinPoint) throws Throwable {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    Object[] args = joinPoint.getArgs();
    Object retVal = joinPoint.proceed(args);
    stopWatch.stop();
    System.out.println(String.format("%sms", stopWatch.getTotalTimeMillis()));
    return retVal;
}
@Around(value = "repositoryOps()")
public Object doLogPerformanceProfiling(ProceedingJoinPoint pjp) throws Throwable {
    long startTime = System.currentTimeMillis();
    String name = "-";
    String result = "Y";
    try {
        name = pjp.getSignature().toShortString();
        return pjp.proceed();
    } catch (Throwable t) {
        result = "N";
        log.error(t);
        throw t;
    } finally {
        long endTime = System.currentTimeMillis();
        log.info(String.format("%s;%s;%sms", name, result, endTime - startTime));
    }
}

8. AOP使用场景

8.1 日志场景

  1. 诊断上下文,入log4j或者logbak中的_x0008_MDC
  2. 辅助信息,如:方法执行时间

8.2 统计场景

  1. 方法调用次数
  2. 执行异常次数
  3. 数据抽样
  4. 数值累加

8.3 安防场景

  1. 熔断,如:Netflix Hystrix
  2. 限流和降级,如:Alibaba Sentinel
  3. 认证和授权,如:Spring Security
  4. 监控,如:JMX

8.4 性能场景

  1. 缓存,如Spring Cache
  2. 超时控制

async_wait
10 声望3 粉丝