❃博主首页 :
「码到三十五」
,同名公众号 :「码到三十五」,wx号 : 「liwu0213」
☠博主专栏 : <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关>
♝博主的话 : 搬的每块砖,皆为峰峦之基;公众号搜索「码到三十五」关注这个爱发技术干货的coder,一起筑基
☠博主专栏 : <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关>
♝博主的话 : 搬的每块砖,皆为峰峦之基;公众号搜索「码到三十五」关注这个爱发技术干货的coder,一起筑基
一、动态代理概念回顾
Java动态代理技术是基于反射机制的基础。核心在于利用反射机制和接口编程在运行时动态生成代理类,并通过InvocationHandler
接口实现代理逻辑的灵活扩展。通过动态代理,Java程序可以在运行时动态地生成代理类,并控制对目标对象的访问,从而实现对目标对象方法的拦截和增强。
其优势在于灵活性、可扩展性、解耦、AOP支持和远程方法调用等方面.
Java动态代理原理主要基于Java的反射机制,通过动态地生成代理类来实现对接口的动态代理。
二、 核心组件
java.lang.reflect.Proxy
类:这是Java动态代理的核心类,提供了创建动态代理实例的静态方法。java.lang.reflect.InvocationHandler
接口:该接口定义了一个方法invoke(Object proxy, Method method, Object[] args)
,代理实例在调用接口方法时,会调用此方法。
三、工作流程
- 定义业务接口:首先,需要定义一个或多个业务接口,这些接口将被动态代理。
- 实现InvocationHandler接口:创建一个实现了
InvocationHandler
接口的类,在该类的invoke
方法中编写代理逻辑。invoke
方法会在代理对象调用接口方法时被自动调用。 - 生成代理实例:通过
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法生成代理实例。这个方法接收三个参数:- ClassLoader loader:类加载器,用于加载代理类。
- Class<?>[] interfaces:一个接口数组,代理类将实现这些接口。
- InvocationHandler h:当代理对象的方法被调用时,会调用此处理器的
invoke
方法。
- 调用代理实例的方法:当调用代理实例的方法时,实际上会调用
InvocationHandler
的invoke
方法,然后可以在invoke
方法中执行自定义逻辑,并调用实际接口实现的方法。
- 动态生成字节码:
Proxy.newProxyInstance
方法内部会动态生成一个实现了指定接口的代理类字节码。这个代理类会重写接口中的所有方法,并在方法内部调用InvocationHandler
的invoke
方法。JDK生成的代理类名称通常为com.sun.proxy.$ProxyN
,其中N
是一个递增的数字,表示这是第几个动态生成的代理类。
四、动态代理的应用场景
动态代理的两个最常用见应用场景为 拦截器 和 声明性接口 :
4.1 搭载器(AOP)
搭载器就是将目标组件劫持,在执行目标组件代码的前后,塞入一些其它代码。比如在正式执行业务方法前,先进行权限校验,如果校验不通过,则拒绝继续执行。对于此类操作,业界已经抽象出一组通用的编程模型:面向切面编程AOP。
- 接口定义和实现
public interface Performer {
void play(String subject);
String introduction();
}
public interface Director {
List<String> getCreations();
}
public class DefaultActor implements Performer {
@Override
public void play(String subject) {
System.out.println("[DefaultActor]: 默认男演员正在即兴表演《" + subject + "》");
}
@Override
public String introduction() {
return "李白·上李邕: 大鹏一日同风起,扶摇直上九万里。假令风歇时下来,犹能颠却沧溟水。世人见我恒殊调,闻余大言皆冷笑。宣父尚能畏后生,丈夫未可轻年少。";
}
}
public class DefaultDirector implements Director {
@Override
public List<String> getCreations() {
List<String> creations = new ArrayList<>();
creations.add("《霸王别姬》");
creations.add("《活着》");
return creations;
}
}
- 拦截器核心类
拦截器核心类实现了InvocationHandler
接口,并在invoke
方法中插入拦截逻辑。
package guzb.diy.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyForInterceptor implements InvocationHandler {
private Object target;
public ProxyForInterceptor(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置拦截逻辑:开始执行 " + method.getName() + " 方法");
Object result = method.invoke(target, args);
System.out.println("后置拦截逻辑:完成执行 " + method.getName() + " 方法");
return result;
}
}
- 测试
public class IntercepterTestMain {
public static void main(String[] args) {
// 创建原始对象
Performer actor = new DefaultActor();
Director director = new DefaultDirector();
// 创建代理对象
Performer actorProxy = (Performer) Proxy.newProxyInstance(
Performer.class.getClassLoader(),
new Class[]{
Performer.class},
new ProxyForInterceptor(actor)
);
Director directorProxy = (Director) Proxy.newProxyInstance(
Director.class.getClassLoader(),
new Class[]{
Director.class},
new ProxyForInterceptor(director)
);
// 通过代理对象调用方法
actorProxy.play("京剧");
System.out.println(actorProxy.introduction());
System.out.println("导演的作品集:");
directorProxy.getCreations().forEach(System.out::println);
}
}
4.2 声明是接口
MyBatis中,声明式接口(通过注解@Select
、@Insert
等)允许直接在接口方法上通过注解来定义SQL语句,而不需要编写具体的SQL实现类。这种方式使得代码更加简洁,易于维护。
使用JDK动态代理来模拟MyBatis中的声明式接口。
-
定义业务接口
先定义一个业务接口,里面包含使用注解定义的SQL操作。
public interface UserMapper { @Select("SELECT * FROM user WHERE name = #{name}") User findUserByName(String name); } // User的POJO类 class User { private Integer id; private String name; // getters and setters }
这里的@Select
注解是自定义的,很简单就不展示了。
-
编写InvocationHandler
写一个InvocationHandler
,它会在运行时解析@Select这些注解,并执行相应的SQL操作。简化版只模拟解析和调用的过程。import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyBatisInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 这里假设能够获取到注解并解析它 // 实际上,你需要一个SQL执行器来执行这里定义的SQL System.out.println("Executing method: " + method.getName()); System.out.println("Parameters: " + Arrays.toString(args)); // 模拟的SQL执行结果 User user = new User(); user.setId(1); user.setName((String) args[0]); return user; } }
-
生成代理对象并调用
最后,我们生成
UserMapper
接口的代理对象,并调用它的方法。import java.lang.reflect.Proxy; public class MyBatisProxyDemo { public static void main(String[] args) { MyBatisInvocationHandler handler = new MyBatisInvocationHandler(); UserMapper mapper = (UserMapper) Proxy.newProxyInstance( UserMapper.class.getClassLoader(), new Class[]{ UserMapper.class}, handler ); User user = mapper.findUserByName("Alice"); System.out.println("Found user: " + user.getName()); } }
输出结果
Executing method: findUserByName
Parameters: [Alice]
Found user: Alice