Java 基础-动态代理
在切面编程(AOP)中,可以方便的对某些业务进行增强,比如添加日志。
在声明式事务中,可以方便的通过 @Transactional 注解开启事务支持。
在 Mybatis 中,我们没有创建 Mapper 接口的实现类,却可以直接注入 Mapper 并进行 CRUD。
而这些底层的实现,就是动态代理。
本篇博客包括以下内容:
- 静态代理
- jdk 动态代理
- cglib 动态代理
一、静态代理
静态代理意味着我们需要在程序运行前创建代理类。
首先,创建 UserService 接口
public interface UserService {
void addUser();
void selectUser(String name);
}
然后创建 UserService 接口的实现类 UserServiceImpl
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加用户...");
}
@Override
public void selectUser(String name) {
System.out.println("查询:" + name);
}
}
假如静态代理需要给 UserService 接口的中的方法添加调用前后的日志记录,那么可以创建 UserServiceProxy
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void addUser() {
System.out.println("日志记录前...");
target.addUser();
System.out.println("日志记录后...");
}
@Override
public void selectUser(String name) {
System.out.println("日志记录前...");
target.selectUser(name);
System.out.println("日志记录后...");
}
}
简单使用静态代理:
UserService target = new UserServiceImpl();
UserService proxy = new UserServiceProxy(target);
proxy.addUser();
proxy.selectUser("ccran");
控制台输出如下:
日志记录前...
添加用户...
日志记录后...
日志记录前...
查询:ccran
日志记录后...
至此,分析一下静态代理的缺点:
- 日志记录的代码存在大量冗余
- 如果需要代理多个接口,需要修改原代理类或创建新的代理类,难以维护和拓展。
二、jdk 动态代理
与静态代理通过 javac 生成.class 字节码文件,然后通过 ClassLoader 加载到 JVM 不同。
动态代理在程序运行时生成字节码文件,通过 Proxy 的 newProxyInstance 方法可以实现 jdk 动态代理。
首先,需要创建 InvocationHandler 接口的实现类 LogInvocationHandler,这个类规定了代理的业务实现,目前是日志记录。
public class LogInvocationHandler implements InvocationHandler {
private UserService target;
public LogInvocationHandler(UserService target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志记录前...");
Object res = method.invoke(target, args);
System.out.println("日志记录后...");
return res;
}
}
通过 Proxy 类的 newProxyInstance 方法可以创建动态代理对象
第一个参数需要传入类加载器,一般传入加载代理类的类加载器
第二个参数需要传入需要代理接口的 class 对象
第三个参数就是之前定义的 InvocationHandler 实现类
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
创建与使用代码如下,输出结果与静态代理的使用相同。
// jdk动态代理
public static void dynamicProxyJDK() {
UserService target = new UserServiceImpl();
InvocationHandler invocationHandler = new LogInvocationHandler(target);
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
invocationHandler);
proxy.addUser();
proxy.selectUser("ccran");
}
通过 ProxyGenerator 的 generateProxyClass 方法将代理类的字节码保存到文件后反编译打开,动态代理的本质就很清晰了。
public final class DynamicUserServiceProxy extends Proxy implements UserService {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m0;
private static Method m3;
public DynamicUserServiceProxy(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 selectUser(String var1) throws {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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);
}
}
public final void addUser() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.ccran.proxy.UserService").getMethod("selectUser", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.ccran.proxy.UserService").getMethod("addUser");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
jdk 动态代理解读:
- 继承 Proxy 类(因此只能代理接口),并实现了 newProxyInstance 方法中第二个参数规定的代理接口。
- 通过 final 保证行为不可更改。
- 在静态代码块中通过反射拿到了代理接口的所有方法。
- 所有方法都是调用 super.h.invoke 方法,在 Proxy 父类中可以发现,super.h 正是 newProxyInstance 方法中第三个参数传入的 LogInvocationHandler 对象(代理的业务实现:日志记录)。
- 调用链:查询用户---DynamicUserServiceProxy(selectUser)---LogInvocationHandler(invoke)---(日志前)被代理对象的 selectUser(日志后)
优点:
解决静态代理的问题。
java 原生代理,无需依赖。
缺点:
只能代理接口。
三、cglib 动态代理
jdk 动态代理无法代理非接口方法,通过 cglib 动态代理可以解决,首先引入 cglib 依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
移除 UserServiceImpl 实现的 UserService 接口,实现 cglib 动态代理
// 动态代理
public static void dynamicProxyCglib() {
UserServiceImpl target = new UserServiceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("日志记录前...");
Object res = methodProxy.invokeSuper(o, objects);
System.out.println("日志记录后...");
return res;
}
});
// 创建代理对象
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
// 通过代理对象调用目标方法
proxy.addUser();
proxy.selectUser("ccran");
}
我们猜测,cglib 可能创建出来的代理类如下:
public final class CglibProxy extends UserServiceImpl{
private MethodInterceptor m;
public final void addUser(){
if(m!=null){
m.intercept(xxx)
}else{
super.addUser();
}
}
}
四、实现
4.1 AOP 实现
AOP 的使用很简单,通过切面表达式定义切点(需要代理的方法,如 addUser),然后配合相关的注解定义增强的方法(代理的业务:如日志记录)即可。
故猜测实现如下(以 jdk 动态代理为例):
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
// 符合切面表达式,是需要代理的方法
if(jointExp.match(method)){
// 封装切点参数
JointPoint jointPoint = getJointPoint(proxy,method,args,xxx);
// 前置通知
beforeMethod.invoke(jointPoint);
res = method.invoke(target, args);
}else{
res = method.invoke(target, args);
}
return res;
}
4.2 声明式事务
对于某个添加了 @Transactional 注解的方法,如
@Transactional
public void insertInfo(){
userService.insertUser();
otherService.insertOther();
}
最终的代理对象方法可能实现如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
conn.setAutoCommit(false);
Object res = null;
try{
res = method.invoke(target, args);
}catch (SQLException e) {
// 回滚事务:
conn.rollback();
} finally {
conn.setAutoCommit(true);
conn.close();
}
return res;
}
4.3 Mapper
在 Mybatis 中,对用户 CRUD 的 Mapper 可能如下:
public interface UserMapper {
@Select("SELECT id,name,age FROM user where id= #{id}")
User selectById(int id);
}
最终注入到 UserService 中的 UserMapper 实现可能如下
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
if(method.isAnnotationPresent(Select.class)){
// 获取sql
Select selectAnnotation = method.getAnnotation(Select.class);
String query = generateQuery(selectAnnotation.value());
// 执行sql
ResultSet resultSet = conn.excuteQuery(sql);
// 封装成对象返回
res = generate(resultSet);
}else if(method.isAnnotationPresent(Update.class)){
xxx
}
xxx
return res;
}
五、总结
- 静态代理比较简单直观,但是会使编写的代码冗余,并且代理多个类时,难以维护和拓展。
- jdk 动态代理本质是实现了代理类实现的接口,因此无法代理非接口方法,但是原生无依赖。
- cglib 动态代理可以代理非接口方法,代码相比于 jdk 动态代理可读性更强。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于