1. 概述
从实现的角度来说,代理分为基于类的代理和基于接口的代理,基于接口的代理有 静态代理和 动态代理,而基于类的代理需要依赖第三方库,比如 cglib
,cglib的代理在运行时动态生成字节码文件来实现代理。
2. 静态代理
在编译期间就已经实现代理
2.1 实现静态代理的必要条件
- 基于接口或者抽象类
- 代理类实现接口或者继承抽象类,通过构造函数传入目标对象
- 重写方法,通过目标对象去执行业务逻辑
- 而代理对象去执行与业务不想关的逻辑
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 静态代理的缺点
- 只能代理一个实现类或接口,无法做到代理多个类,这样照成了代理类的作用范围的局限性
- 对于不需要代理的接口方法也需要实现,造成的代码的冗余,扩展难的问题
- 依赖于接口
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 与静态代理的区别
- 静态代理在编译期就已经完成,而动态代理在运行时完成
- 静态代理只能代理一个类,而动态代理可以代理多个类
- 都依赖与接口
- 静态代理扩展难以及难以维护,而动态代理代理了接口中的所有的方法,达到了通用性。
- 由于动态代理代理了接口中的所有方法,如果目标对象需要扩展接口方法且不依赖代理对象就需要关注代理的实现细节。
4. CgLig 代理
cglib
全称Code Generation Libary
,即 代码生成库,能能够在运行时生成子类对象从而达到对目标对象扩展的功能。
4.1 优点
- 无论是静态代理还是动态代理都依赖与接口,而
cglib
可以代理一个类,这样就达到了代理类的无侵入性。 - 在运行时动态生成字节码文件
4.2 代码示例
要是用cglib
代理,我们需要引入 cglib的依赖:
cglib
cglib
3.2.5
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("[email protected]");
}
}
5. 小结
- 静态代理实现比较简单,通过代理对象对目标对象进行包装,就可以实现增强功能,但静态代理只能对一个类进行代理,如果代理目标对象过多则会产生很多代理类。
- JDK代理也就是动态代理,代理类需要实现
InvocationHandler
接口 - 静态代理在编译生成字节码文件,直接使用,效率高。
- 动态代理和
cglib
代理基于字节码,在运行时生成字节码文件,效率比静态代理略低。 - 静态代理和动态代理都是基于接口的,而
cglib
可以基于类进行代理 cglib
代理在运行时生成代理对象的子类,重写该子类的方法来实现代理,因此代理对象被代理的方法不能被final
修饰,代理类需要实现MethodInterceptor
。
6. AOP
Aspect-oriented programming
,即面向切面编程,将代码进行模块划分,降低耦合,分离关注点,减少对业务代码的侵入性。
在面向对象中,我们有三大核心特征,分别是:
- 封装
- 继承
- 多态
这三种特性中使用的最多的就是多态,加上由于使用组合代替继承降低对父类的依赖不再关心实现细节,而实现多态更多的抽象出一个顶级接口,因此我们从面向对象的封装特称抽象出"面向接口编程"这个概念。而AOP
是对面向对象的补充,面向对象以"类"作为基本单元,根据不同的业务场景去将我们的类进行模块化,我们的AOP
模块化的关键是切面/Aspect
,Aspect
不再关心具体的类型,通过代理技术对目标对象进行增强。
其中代理又分为基于class
和interface
的代理,基于class
的代理我们一般指的是aspectJ
,而基于interface
的代理则是静态代理和动态代理。
6.1 为什么要使用AOP
6.1.2 代码无侵入性
在不改变类结构的情况下,动态的生成字节码对类进行扩展。
6.1 AOP相关概念
6.1.1 Aspect
首先第一个就是我们的aspect
,aspect
类似于我们的类,在很多工具类中,它表达的更多其实是通用函数的一个容器,用于保存与这个函数容器相关的方法,而aspect
就是pointCut
、JoinPoint
、Advice
的容器。
一个aspect
可以包含多个pointCut
、Advice
,但是只能包含一个JoinPoint
,因此关系是一对一对多对多。
6.1.2 PointCut
在Spring AOP
中我们只能对方法进行拦截,但并不是所有方法都需要我们进行一个增强,因此PointCut
就是Spring AOP
给我们提供的对方法进行过滤的一个功能,在Spring
官方文档中把它称为 切入点。
6.1.3 JoinPoint
6.1.4 Advice
在拦截了需要代理之后,我们可以就可以对方法进行增强,Spring
给我们提供以下执行增强的动作:
- around
- before
- after-returning
- after-throwing
- (finally)after
before
、after-returning
、after-throwing
这都很好理解,分别是方法执行之前的动作、方法正常返回的动作、方法异常退出的动作。而finally-after
与try-catch-finally
如果把after-retruning
比作try
,把after-throwing
比作catch
,那么after
就是finally
。
我们可以简单对比try-catch-finally
的执行流程来理解after-retruning
,after-thrwoing
,finally-after
。
- 在
try
块中没有抛出异常,那么不会走catch
。 - 在
try
中抛出异常且被catch
捕获,如果方法抛出那么会终止try
后续的代码执行catch
。 - 除非
JVM
虚拟机退出,否者一定执行finally
在Spring-AOP
执行的动作中:
- 如果方法正常执行没有抛出异常,那么不会执行
after-throwing
- 如果方法执行过程中抛出了异常,那么会执行
after-throwing
- 无论有没有抛出异常都会执行
finally-after
在try-catch-finaly
中,我们可以根据finally
这个特性来执行一些资源释放的操作,比如数据库的关闭、IO的关闭。同样的,在after-finlaly
中我们可以执行一些资源释放操作,比如释放锁。
7. Spring 对 AOP的整合
要使用apring-aop
我们需要引入Spring
对AspectJ
的支持:
org.springframework
spring-aspects
${spring.framework.version}
7.1 启用AspectJ的支持
7.1.1 Java Configuration
@EnableAspectJAutoProxy
public class AspecJConfiguration{
}
7.1.2 XML Configuration
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 日志场景
- 诊断上下文,入
log4j
或者logbak
中的_x0008_MDC
- 辅助信息,如:方法执行时间
8.2 统计场景
- 方法调用次数
- 执行异常次数
- 数据抽样
- 数值累加
8.3 安防场景
- 熔断,如:
Netflix Hystrix
- 限流和降级,如:
Alibaba Sentinel
- 认证和授权,如:
Spring Security
- 监控,如:
JMX
8.4 性能场景
- 缓存,如
Spring Cache
- 超时控制