先通过了解代理模式的原理 ,希望知道动态代理是怎么实现的动态增强;
然后再具体看下spring AOP是如何使用动态代理实现的切面、织入等逻辑。
文章目录
一. 代理模式
1. 代理的概念
代理模式是指client调用一个对象时,通过代理实例调用(并增强)此对象,而不是直接调用它。
2. 静态代理
看下静态代理的实现思路,为理解动态代理提供一些基础
实现的基本思路:
通过实现同一个接口(用于实现方法增强)+ 接口聚合到代理类中。如下代码:
//代理和被代理对象都要实现的接口
public interface ITeacherDao {
void teach(); // 授课的方法
}
//被代理对象
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println(" 老师授课中 。。。。。");
}
}
//静态代理对象
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao target; //聚合:被代理对象
public TeacherDaoProxy(ITeacherDao target) {//面向接口编程:
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理 完成某些操作。。。。。 ");//增强
target.teach(); //被代理对象的方法
System.out.println("提交。。。。。");//增强
}
}
//client:将被代理类放入到代理类,然后通过创建代理类,来调用被代理对象的方法。
TeacherDao teacherDao = new TeacherDao();
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
teacherDaoProxy.teach();
小结:
实现逻辑其实不难,就是代理class、被代理class都实现一个接口,且代理class的方法实现了被代理对象的增强。
这里的增强需要注意:
- 使用了面向接口编程,即可以根据被代理class的不同,调用不同的被代理instance的方法;
- 增强:在执行时,会在被执行方法前后进行添加一些执行逻辑。
上面的逻辑其实解释了代理的基本要素:
- 被代理对象的方法是代理对象去调用,而不是自己调用
- 因为调用被代理,所以可以在被调用前后进行增强。
但静态代理的缺点也比较明显:每个被代理类都需要手动去实现代理类,如果需要代理的类比较多,那代码是比较臃肿的。
3. 动态代理
3.1. 概述
动态代理的代理类并不是在Java代码中定义的,而是在JVM运行时根据我们在Java代码中的“指示”动态生成的。
所以相比于静态代理,动态代理可以统一处理被代理类的所有方法,而不用修改每个代理类的方法。
3.2. 代码实现
//接口
public interface ITeacherDao {
void teach();
void sayHello(String name);
}
//目标类(被代理类)
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println(" 老师授课中.... ");
}
@Override
public void sayHello(String name) {
System.out.println("hello " + name);
}
}
//代理工厂
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象 生成一个代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), //1. 类加载器:创建代理类时检查
target.getClass().getInterfaces(), //2. 接口:创建代理类要素1
new InvocationHandler() { //3. 代理逻辑:创建代理类要素2
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理开始~~");
Object returnVal = method.invoke(target, args);
System.out.println("JDK代理提交");
return returnVal; }
});
}
}
ITeacherDao target = new TeacherDao();
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
proxyInstance.teach();
proxyInstance.sayHello(" tom ");
这里我们可以将invocationHandler看作是一个代理类,携带着被代理对象,在invoke方法中调用了被代理对象的方法,并可以在方法前后进行增强。
3.3. 源码理解
动态构建动态代理类的逻辑
我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,看下源码具体是怎么实现的:
//java.lang.reflect.Proxy
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
... 一些检查和校验
final Class<?>[] intfs = interfaces.clone();
//生成代理类:通过类加载器 + 代理接口
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
... 一些检查和校验
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//通过反射构造出来的构造器实现了动态代理对象
return cons.newInstance(new Object[]{h});
} catch 。。。
}
源码的基本逻辑:
先通过
Class<?> cl = getProxyClass0(loader, intfs)
获取到了代理class实例,然后通过类实例创建了构建器,进而实现了代理对象。
执行getProxyClass0
方法时会动态的生成一个代理类文件,下面我们看一下类文件的具体内容:
//动态生成的代理类文件
//0表示生成的第一个代理类,如果有多个序号会依次递增
public final class $Proxy0 extends Proxy implements Person{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
*这里是代理类的构造器,注意看入参数类型是InvocationHandler
*
*super(paramInvocationHandler),是调用父类Proxy的构造方法。
*父类持有:protected InvocationHandler h;
*Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*/
public $Proxy0(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}
static{
try{
//注意看:giveMoney通过反射得到方法,名字是m3
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} catch ...
}
/**
*直接就调用了InvocationHandler中的invoke方法,并把m3传了进去,这里的m3其实就是被代理方法
*看到这里大概知道了动态代理的本质是:通过InvocationHandler去调用被代理类的方法
*/
public final void giveMoney() throws {
try{
this.h.invoke(this, m3, null);
return;
}catch ...
}
... toString,hashCode、equals方法的内容。
}
小结一下上面反编译的class:
代理类的生成:
- 首先$Proxy0是动态生成的代理类,0代表第一个代理类,如果有多个(被代理)实例,序号会依次递增;
- 代理类中会生成相同签名的方法
代理调用:
- 当执行方法时,会找到代理类中相同签名的方法,进行执行;
- invoke执行:每个代理类中都有InvocationHandler,其实代理工作是他做的,即他持有被代理对象,通过invoke方法(如上例实践),增强并执行(被代理)实例方法。
二. Spring AOP中的代理原理
Spring AOP是基于代理实现的,默认为标准的 JDK 动态代理。参考上述动态代理的讲解(接口+对象的加载器),我们可以知道,spring可以使得任何接口(参考3.2 代理工厂)(或者接口的集合)可以被代理。
值得关注的是:
将被代理对象A交给代理对象B执行,B执行A方法时,可以在A方法执行前后进行增强,但并不能改造A对象方法内部。
1. 一个简单的场景
有10个方法在执行前想进行一些校验(校验的逻辑都一致),方法执行完之后统一进行返回值的处理,统一返回给某处。
2. AOP概念分析
先看下对于上面的场景我们需要关注那些点,spring是怎么做到的:
- 指定对哪些方法进行增强:切(入)点,程序中通过切入点表达式来表示切点;
- 对这些方法怎么(前、后、围绕还是异常时)进行增强:Advice(通知);
- 怎么组织切点和通知:通过切面。
其此在程序运行过程中,对于目标方法spring这边通过JointPoint(连接点)去表示。
简单来说我们构建一个AOP逻辑:先要确定切点和通知,知道哪些方法要被拦截进行增强,当连接点要执行时,进行拦截增强,而这些逻辑都被切面进行组织。
3. 实操
@Aspect
@Component
@Slf4j
public class SchedulerAspect {
@Resource
private NacosService nacosService;
@Pointcut("execution(* xxx.statictask.FlinkRefreshTaskDataScheduler.*(..) )&&" +
" @annotation(org.springframework.scheduling.annotation.Scheduled)")
public void pointCut() {
}
@Around("pointCut()")
public Object auditLogCut(ProceedingJoinPoint joinPoint) {
Object result = null;
if (!nacosService.isApiOldestInstance()) {
return result;
}
try {
result = joinPoint.proceed();
} catch (Throwable throwable) {
log.error(throwable.getMessage());
}
return result;
}
}
简单描述下AOP的逻辑:
@Aspect
声明了一个切面逻辑,其中我们要进行增强的方法有:FlinkRefreshTaskDataScheduler
类下所有添加了@Scheduled
注解的方法,通过@Around
下auditLogCut
方法规定了增强的逻辑:当方法执行前判断此进程是否时nacos注册最早的节点,如果是我就允许你执行定时任务,如果不是则不能执行。