1、引言
在Java编程中,代理模式是一种常见的设计模式,用于在不修改原始代码的情况下,为对象添加额外的功能。代理模式有两种主要类型:静态代理和动态代理。本文将全面探讨这两种代理模式,包括它们的基本概念、实现方式、应用场景及其优缺点。
2、静态代理
静态代理是在编译时确定代理类的。代理类和目标类都需要实现相同的接口,代理类持有对目标对象的引用,并在调用目标对象的方法前后插入额外的逻辑。
先给大家看一张基础设计代理的原理图
2.1、静态代理的示例
定义一个接口
public interface ISubject{
void eat();
}
实际类
public class RealSubject implements ISubject{
@Override
public void eat() {
System.out.println("RealSubject do eat...");
}
}
代理对象
public class ProxyObject implements ISubject{
private ISubject iSubject;
public ProxyObject(ISubject iSubject) {
this.iSubject = iSubject;
}
private void preSomething() {
System.out.println("preEat......");
}
private void afterSomething() {
System.out.println("afterEat......");
}
@Override
public void eat() {
preSomething();
iSubject.eat();
afterSomething();
}
}
工厂类
public class ObjectFactory {
public static ISubject getInstance(){
return new ProxyObject(new RealSubject());
}
}
调用
public class TestProxy {
public static void main(String[] args) {
testProxy();
}
public static void testProxy(){
ISubject iSubject = ObjectFactory.getInstance();
iSubject.eat();
}
}
上面这个是最简单的例子,我们一般在使用的代理模式的时候,我们只关注代理是谁,真实对象是谁即可,如何根据这两个参数(也就是类的全限定名称)进行代理呢?我们可以使用反射,下来我们重构一下工厂类和main方法,示例代码如下:
public class ObjectFactory {
public ObjectFactory() {}
/**
* 通过类名字符串创建并返回指定类型的实例
*
* @param className 要创建的类的全限定名字符串
* @return 创建的实例
* @throws RuntimeException 如果指定的类不存在,或者实例化过程中出现错误,则抛出运行时异常
*/
public static <T> T getInstance(String className){
T t = null;
try {
// 通过反射机制创建指定类的实例
t = (T) Class.forName(className).newInstance();
} catch (InstantiationException e) {
// 如果类无法被实例化,则抛出运行时异常
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// 如果类的构造方法不可访问,则抛出运行时异常
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
// 如果类不存在,则抛出运行时异常
throw new RuntimeException(e);
}
return t;
}
/**
* 根据代理名和真实对象名创建并返回一个代理对象实例
*
* @param proxyName 代理类的全限定名,用于动态加载和实例化代理对象
* @param realName 真实对象类的全限定名,用于获取真实对象的实例
* @param <T> 泛型标记,表示返回的代理对象类型
* @return T 返回一个实现了真实对象接口的代理对象实例
* @throws RuntimeException 如果在实例化代理对象过程中发生错误,则抛出运行时异常
*
* 此方法主要用于在运行时动态创建代理对象,通过反射机制加载并实例化指定的代理类
* 代理类需要通过构造方法接收真实对象作为参数,以便代理对象能够调用真实对象的方法
*/
public static <T> T getInstance(String proxyName, String realName){
// 初始化代理对象为null
T t = null;
// 获取真实对象的实例
T obj = getInstance(realName);
// 尝试通过反射机制实例化代理对象
try {
// 使用代理类名和接口类型来创建代理对象实例
t = (T) Class.forName(proxyName).getConstructor(obj.getClass().getInterfaces()[0]).newInstance(obj);
} catch (InstantiationException e) {
// 如果代理类无法实例化,则抛出运行时异常
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// 如果构造方法非法访问,则抛出运行时异常
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
// 如果调用构造方法时目标方法抛出异常,则抛出运行时异常
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
// 如果未找到符合要求的构造方法,则抛出运行时异常
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
// 如果未找到代理类,则抛出运行时异常
throw new RuntimeException(e);
}
// 返回创建的代理对象实例
return t;
}
}
调用
public class TestProxy {
public static void main(String[] args) {
testReflect();
}
public static void testReflect(){
ISubject iSubject = ObjectFactory.getInstance("com.hy.proxy.ProxyObject", "com.hy.proxy.RealSubject");
iSubject.eat();
}
}
2.2 静态代理的优缺点
优点:
- 代码清晰,易于理解。
- 适合于需要明确代理逻辑的场景。
缺点:
- 每个代理都需要一个对应的目标类,代码冗余。
- 不够灵活,代理类和目标类的关系在编译时确定。
3、动态代理
动态代理是在运行时创建代理对象的,能够在运行时决定具体的代理逻辑。Java的动态代理分为两种主要实现方式:
- JDK动态代理
- CGLIB动态代理
对于动态代理,代理对象可以代理一个类中的多个实现方法,即一个真实类实现多个接口,一个代理类就可以完全代理
3.1、JDK动态代理
JDK动态代理基于反射机制,可以在运行时创建代理对象。要使用JDK动态代理,目标类必须实现一个或多个接口。通过 Proxy 类和 InvocationHandler 接口,可以创建代理对象,并在调用方法时插入额外的逻辑。
3.1.1、JDK动态代理示例代码
接口
public interface ITest {
void play();
}
public interface ISubject{
void eat();
}
真实类
public class DynamicRealObject implements ISubject, ITest{
@Override
public void eat() {
System.out.println("DynamicRealObject eat...");
}
@Override
public void play() {
System.out.println("DynamicRealObject play...");
}
}
动态代理类
public class DynamicProxy implements InvocationHandler {
private Object target;
/**
* 绑定目标对象并生成代理对象
* 本方法主要用于通过动态代理绑定一个目标对象,并返回该目标对象的代理实例
*
* @param target 目标对象,即需要通过代理的对象
* @return 返回目标对象的代理实例,该实例可以用于方法拦截和调用
*/
public Object bind(Object target) {
// 绑定目标对象到当前代理实例
this.target = target;
// 利用Java动态代理机制创建并返回目标对象的代理实例
// 该代理实例将使用目标对象的类加载器,实现目标对象所实现的所有接口,并将当前代理实例作为调用处理程序
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
private void preSomething() {
System.out.println("preEat......");
}
private void afterSomething() {
System.out.println("afterEat......");
}
/**
* 该方法通过Java动态代理机制,拦截调用目标方法的请求
* 在调用目标方法之前和之后执行特定操作,实现环绕通知的功能
* 这种方式可以用于实现事务管理、日志记录、权限控制等横切关注点
*
* @param proxy 代理对象,通常不用直接操作
* @param method 被调用的目标方法的信息
* @param args 调用目标方法时传递的参数
* @return 目标方法的返回值
* @throws Throwable 目标方法抛出的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用目标方法之前执行的操作,如开启事务、日志记录等
preSomething();
// 调用目标方法,并传递参数
Object obj = method.invoke(target, args);
// 在调用目标方法之后执行的操作,如关闭事务、日志记录等
afterSomething();
// 返回目标方法的执行结果
return obj;
}
}
调用
public class TestProxy {
public static void main(String[] args) {
testDynamicProxy();
}
public static void testDynamicProxy(){
// 结合静态代理的反射,可以这样写
// ISubject iSubject = (ISubject) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));
// ITest iTest = (ITest) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));
ISubject iSubject = (ISubject) new DynamicProxy().bind(new DynamicRealObject());
iSubject.eat();
ITest iTest = (ITest) new DynamicProxy().bind(new DynamicRealObject());
iTest.play();
}
}
3.2、CGLIB动态代理类
CGLIB(Code Generation Library)是一个功能强大的代码生成库,允许在运行时动态创建代理类。与JDK动态代理不同,CGLIB不要求目标类实现接口,而是通过继承目标类来创建代理。
3.2.1 CGLIB动态代理示例代码
目标类
public class CglibProxy {
public void doSomething(){
System.out.println("Cglib do something");
}
}
代理类
public class CglibInterceptor implements MethodInterceptor {
private final Object target;
public CglibInterceptor(Object target) {
this.target = target;
}
private void preDoSomething() {
System.out.println("preDoSomething......");
}
private void afterDoSomething() {
System.out.println("afterDoSomething......");
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
preDoSomething();
Object obj = method.invoke(target, objects);
afterDoSomething();
return obj;
}
}
调用
public class TestProxy {
public static void main(String[] args) {
testCglib();
}
/**
* 使用CGLIB进行动态代理的测试方法
* 该方法通过CGLIB框架创建一个CglibProxy的子类实例,以实现方法的拦截和调用
*/
public static void testCglib(){
// 创建CGLIB增强器实例
Enhancer enhancer = new Enhancer();
// 设置代理类的父类为CglibProxy类,这样CGLIB会生成该类的子类
enhancer.setSuperclass(CglibProxy.class);
// 设置拦截器,传入CglibProxy实例作为拦截器的关键信息
enhancer.setCallback(new CglibInterceptor(new CglibProxy()));
// 通过增强器创建代理类实例
CglibProxy cglibProxy = (CglibProxy) enhancer.create();
// 通过代理实例调用目标方法,此调用将被CglibInterceptor拦截并处理
cglibProxy.doSomething();
}
}
3.3、动态代理的优缺点
优点:
- 灵活性高,可以在运行时创建代理对象。
- 不需要修改目标类代码。
缺点:
- JDK动态代理只能代理实现了接口的类,不能代理具体类。
- CGLIB动态代理在生成代理类时会继承目标类,这可能导致一些问题,如不能代理final类或方法。
3.4 CGLIB和JDK动态代理区别
具体区别如下:
-
实现原理: JDK动态代理是基于Java反射机制实现的,它要求目标类必须实现一个或多个接口,代理对象在运行时动态创建,通过实现目标类接口的方式来代理目标类。 CGLIB代理则是基于ASM字节码框架实现的,它可以代理没有实现接口的目标类。CGLIB在运行时通过动态生成目标类的子类来实现代理。
-
性能表现: JDK动态代理因为需要实现目标类接口,所以它的性能相对较低,但是它的应用场景更为广泛,适用于大多数情况下的代理需求。 CGLIB代理则因为不需要实现目标类接口,所以它的性能相对较高,但是它不能代理final类和final方法,以及一些无法生成子类的类。
-
应用场景: JDK动态代理适用于代理接口的场景,例如Spring中的事务处理、日志记录等。 CGLIB代理适用于代理类的场景,例如Spring中的AOP切面编程等。