代理模式是啥?
在不修改目标对象的前提下,扩展目标对象的功能。Spring Boot的面向切面编程就使用了动态代理+反射。
静态代理
静态代理比较简单:
- 定义接口
- 目标类实现该接口;代理类实现接口,并将目标类作为成员变量
- 使用时,将目标类对象传入代理类对象,通过代理类调用对应的方法
interface SmsService {
void send();
}
class SmsServiceImpl implements SmsService {
@Override
public void send() {
System.out.println("send message.");
}
}
class SmsServiceProxy implements SmsService {
SmsServiceImpl smsServiceImpl;
public SmsServiceProxy(SmsServiceImpl smsServiceImpl) {
this.smsServiceImpl = smsServiceImpl;
}
@Override
public void send() {
System.out.println("send before.");
smsServiceImpl.send();
System.out.println("send after.");
}
}
public class Main {
public static void main(String[] args) {
SmsServiceProxy smsServiceProxy = new SmsServiceProxy(new SmsServiceImpl());
smsServiceProxy.send();
}
}
静态代理很明显的缺点就是,代理类要去实现指定的接口,你需要针对每一个被代理的类去实现代理类;接口改动,就要大动干戈!要抽象出通用代理类我们很容易想到用类似泛型的方法:根据传入的类、指定的方法和参数不同去调用代理。要实现这个需求,就需要用到反射了,这就是动态代理啦。
动态代理
动态代理通常要结合反射来实现;(因为要在运行时动态获取目标对象的类和方法信息)
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
这句话很抽象,但是我觉得我可以理解,因为动态代理是在运行过程中才得知自己代理的类,所以肯定是要动态生成字节码来执行的。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
接下来的内容是copy过来的,动态代理
JDK动态代理机制
JDK动态代理核心:Proxy 类和InvocationHandler 接口;
Proxy用来生成一个代理对象(相当于静态代理的SmsServiceProxy);InvocationHandler 接口实现代理逻辑(相当于静态代理的send方法)
Proxy类关键方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
/*
* Look up or generate the designated proxy class.
*/
// 生成目标类的代理类(运行时生成)
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
// 获得代理类构造器
// private static final Class<?>[] constructorParams = { InvocationHandler.class };
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h; // 这行代码很奇怪,定义了ih但是没有使用
// 反射创建实例
return cons.newInstance(new Object[]{h});
}
- loader:类加载器,用于加载代理对象。
- interfaces:被代理类实现的一些接口;
- h:实现了InvocationHandler 接口的对象;
InvocationHandler接口
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
- proxy:代理类
- method:代理方法
- args:方法参数
- 返回值为Object
我们先来写个例子
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new JdkInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}
public class JdkInvocationHandler implements InvocationHandler {
private Object target;
public JdkInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before.");
Object result = method.invoke(target, args);
System.out.println("after.");
return result;
}
}
public class Main {
public static void main(String[] args) {
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send();
}
}
JDK动态代理只能代理实现接口的类,我们来看看生成的代理类是怎么样的
(VM Options:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true生成代理类.class)
// final代理类,继承Proxy并实现了被代理类实现的接口,所以代理类可以强转为对应接口
final class $Proxy0 extends Proxy implements SmsService {
// 表示代理类实现了4个方法
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// 调用父类的构造方法,主要是初始化InvocationHandler,用来执行invoke()方法
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void send() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 静态代码块,通过反射获取method实例
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("org.example.proxy.SmsService").getMethod("send");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
代理类在执行每个方法都会调用h.invoke();代理类本身、Method实例、入参三个参数进行传递。
代理类具体怎么创建的,这里不再继续讨论,等以后有机会再看源码。JDK动态代理:不仅要学会用,更要掌握其原理
JDK动态代理有一个很关键的点在于只能代理实现接口的类;为了解决这个问题,可以使用GCLIB动态代理机制
CGLIB 动态代理机制
CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。c底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${version}</version>
</dependency>
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
public CglibProxyFactory(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before.");
Object result = proxy.invoke(target, args);
System.out.println("after.");
return result;
}
public Object getProxy() {
// 使用 Enhancer 类生成动态子类
Enhancer enhancer = new Enhancer();
// 设置被代理类
enhancer.setSuperclass(target.getClass());
// 设置类加载器
enhancer.setClassLoader(target.getClass().getClassLoader());
// 设置方法拦截器
enhancer.setCallback(this);
// 创建代理类
return enhancer.create();
}
}
Cglib是通过实现被代理类的子类实现动态代理,以后有空再看代理类,现在累了,不想写了。
JDK 动态代理和 CGLIB 动态代理对比
- JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
Spring AOP
面向切面编程,懂得都懂;不懂的就算了。
概念
- 连接点(Joinpoint):程序执行过程中的某一行为;(就是可以当切点的所有点?)
- 切点(Pointcut):在哪
- 通知(Advice):“切面”对于某个“连接点”所产生的动作;什么时候干什么
- -切面(Aspect):切点+通知,在哪,什么时候,干啥
说实话,这几个概念是真的抽象,但是不重要
// 定义切面
@Aspect
public class AopAdvice {
// 定义切点
@Pointcut("execution(* org.example.proxy.SmsServiceImpl.send(..))")
public void pointcut() {
}
// 定义前置通知
@Before("pointcut()")
public void before() {
System.out.println("before.");
}
// 定义后置通知
@After("pointcut()")
public void after() {
System.out.println("after.");
}
// 定义环绕通知
@Around("pointcut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("before.");
proceedingJoinPoint.proceed();
System.out.println("after.");
}
}
关于切点的定义: