欢迎参观我的博客,一个Vue 与 SpringBoot结合的产物:https://poetize.cn
- 博客:https://gitee.com/littledokey/poetize-vue2.git
- 聊天室:https://gitee.com/littledokey/poetize-im-vue3.git
- 后端:https://gitee.com/littledokey/poetize.git
- 七牛云登录/注册地址(文件服务器,CDN):https://s.qiniu.com/Mz6Z32
原文链接:https://poetize.cn/article?id=46
动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件
主要用处
Spring AOP、日志、用户鉴权、全局性异常处理、性能监控等
代理模式角色
- Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法
- RealSubject(真实主题角色):真正实现业务逻辑的类
- Proxy(代理主题角色):用来代理和封装真实主题
JDK动态代理代码示例
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
。
代理类和所有方法都被public final
修饰,所以代理类只可被使用,不可以再被继承。
/**
* 任务接口
*/
public interface BaseTask extends Serializable {
/**
* 任务执行前置处理
*/
default void before(String[] args) throws Exception {
}
/**
* 执行任务
*/
void run(String[] args) throws Exception;
/**
* 任务执行后置处理
*/
default void after(String[] args) throws Exception {
}
/**
* 任务执行失败后回滚处理
*/
default void rollback(String[] args) throws Exception {
}
}
/**
* 代理类
*/
@Slf4j
public class TaskProxy implements InvocationHandler {
private BaseTask target;
public TaskProxy(Class<? extends BaseTask> clazz) throws Exception {
this.target = clazz.getConstructor().newInstance();
}
/**
* 任务执行前置处理
*/
private void before(String[] args) throws Exception {
//加载环境参数配置
EnvContext.loadEnvArgs();
target.before(args);
}
/**
* 任务执行失败后回滚处理
*/
private void rollback(String[] args) throws Exception {
try {
target.rollback(args);
} catch (Exception e) {
log.error("任务执行失败后回滚异常", e);
}
}
/**
* 任务执行后置处理
*/
private void after(String[] args) throws Exception {
try {
target.after(args);
} catch (Exception ignored) {
}
try {
JdbcContext.closeConnections();
} catch (Exception ignored) {
}
}
/**
* @param proxy 代理
* @param method 方法
* @param args 运行参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String[] stringArgs = (String[]) args;
try {
before(stringArgs);
return method.invoke(target, args);
} catch (Exception e) {
log.error("任务处理发生异常:{}", e.getMessage());
rollback(stringArgs);
throw e;
} finally {
after(stringArgs);
}
}
/**
* 获取被代理对象
*/
public BaseTask getProxyInstance() {
return (BaseTask) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
/**
* 使用
*/
public class TaskExecutor {
private TaskExecutor() {
}
public static void run(String[] args, Class<? extends BaseTask> clz) throws Exception {
new TaskProxy(clz).getProxyInstance().run(args);
}
}
JDK动态代理特点
- JDK的代理是利用反射生成
Proxyxx.class
代理类字节码,并生成对象 - JDK动态代理是基于接口设计实现的,如果没有接口,会抛异常
- JDK动态代理之所以只能代理接口是因为代理类本身已经
extends Proxy
,而Java是不允许多重继承的,但是允许实现多个接口
CGLIB代理特点
- CGLib采用了字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,织入横切逻辑,来完成动态代理的实现。
- 由于CGLib是采用动态创建子类的方法,对于
final
方法,无法进行代理。 - CGLib在创建代理对象时所花费的时间比JDK多得多,所以对于频繁创建对象,使用JDK方式要更为合适一些。
Spring AOP
AOP的技术,主要分为两大类:
- 一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行。
- 二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。
默认的策略是如果目标类是接口,则使用JDK动态代理技术;如果目标对象没有实现接口,则默认会采用CGLIB代理。
Java中创建对象的5种方式
Person person = new Person();
//Class类的newInstance使用的是类的public的无参构造器
Person person = Person.class.newInstance();
//有参数(不再必须是无参)的和私有的构造函数(不再必须是public)
Constructor<?>[] declaredConstructors = Person.class.getDeclaredConstructors(); //包括public的和非public的,当然也包括private的
Constructor<?>[] constructors = Person.class.getConstructors(); //只返回public的(返回结果是上面的子集)
Constructor<?> noArgsConstructor = declaredConstructors[0];
Constructor<?> haveArgsConstructor = declaredConstructors[1];
noArgsConstructor.setAccessible(true); //非public的构造必须设置true才能用于创建实例
Object person1 = noArgsConstructor.newInstance();
Object person2 = declaredConstructors[1].newInstance("小明", 18);
//Clone
public class Person implements Cloneable
//反序列化
deserialize