代理模式是一种常见的软件设计模式。所谓的代理者是指一个类别可以作为其它对象的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它重要或无法复制的资源。
有时候我们需要需要一个功能(通常是一个API
)。该功能已经被某个类A
实现了,代理类B
实现相同的接口,并将操作交给A
去处理,在这个过程中可以添加自己的功能。
之前只是听过一些说法,并未对Java代理
进行学习,前些日子抽空学习了一点点,下面分享我在Java
使用代理模式的一些Demo
,主要分三类:静态代理、JDK 动态代理和cglib 动态代理。
我们从一个接口IUserProvider
开始:
package com.fun.ztest.proxytest;
public interface IUserProvider {
User getUser(int i);
}
此接口由UserProviderImpl
实现:
package com.fun.ztest.proxytest;
import com.fun.frame.SourceCode;
import com.fun.utils.RString;
public class UserProviderImpl extends SourceCode implements IUserProvider {
@Override
public User getUser(int i) {
output("我是原配实现类!");
return new User(RString.getString(i), getRandomInt(i), null);
}
}
现在让我们为IUserProvider
添加一个代理对象,该对象执行一些简单的日志记录:
package com.fun.ztest.proxytest;
import com.fun.frame.SourceCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogProxy extends SourceCode implements IUserProvider {
private static Logger logger = LoggerFactory.getLogger(LogProxy.class);
private IUserProvider userProvider;
public LogProxy(IUserProvider userProvider) {
this.userProvider = userProvider;
}
@Override
public User getUser(int i) {
logger.info("获取用户{}", i + EMPTY);
return userProvider.getUser(i);
}
}
我们要为IUserProvider
创建代理,因此我们的代理需要实现IUserProvider
。在构造函数中,AutoLogProxy
类需要传入一个实际的IUserProvider
实现类UserProviderImpl
对象。然后在getUser
方法中添加了一行日志,最终的操作还是交给IUserProvider
实现类UserProviderImpl
对象去完成。
要使用我们的代理,我们必须更新初始化代码:
public static void main(String[] args) {
IUserProvider userProvider = new UserProviderImpl();
LogProxy logProxy = new LogProxy(userProvider);
logProxy.getUser(12);
}
INFO-> 当前用户:fv,IP:10.60.131.54,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> 获取用户12
INFO-> 我是原配实现类!
Process finished with exit code 0
这个解决方案有一个主要缺点:代理实现绑定到IUserProvider
接口,因此很难重用。
代理逻辑通常很通用。代理的典型用例包括缓存、远程对象或延迟加载。
但是,代理需要实现特定的接口(及其方法),这与可重用性矛盾。
JDK
提供了针对此问题的标准解决方案,称为Dynamic Proxies
。使用动态代理,我们可以在运行时为特定接口创建实现。在此生成的代理上的方法调用被委派给InvocationHandler
。
下面是代理类的代码:
package com.fun.ztest.proxytest;
import com.fun.frame.SourceCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
public class AutoLogProxy extends SourceCode implements InvocationHandler {
private static Logger logger = LoggerFactory.getLogger(AutoLogProxy.class);
private final Object drive;
public AutoLogProxy(Object invocationTarget) {
this.drive = invocationTarget;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logger.warn("调用方法:{},参数:{}",method.getName(), Arrays.toString(args));
return method.invoke(drive, args);
}
}
下面是使用方法:
public static void main(String[] args) {
IUserProvider userProvider = new UserProviderImpl();
IUserProvider provider = (IUserProvider) Proxy.newProxyInstance(IUserProvider.class.getClassLoader(), new Class[]{IUserProvider.class}, new AutoLogProxy(userProvider));
provider.getUser(10);
}
INFO-> 当前用户:fv,IP:10.60.131.54,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
WARN-> 调用方法:getUser,参数:[10]
INFO-> 我是原配实现类!
Process finished with exit code 0
使用Proxy.newProxyInstance
,我们创建一个新的代理对象。此方法采用三个参数:
InvocationHandler
接口实现invoke
方法有三个参数:
代理创建(和接口实现)和代理逻辑(通过InvocationHandler
)的分离支持可重用性。注意,我们在InvocationHandler
实现中对IUserProvider
接口没有任何依赖。在构造函数中,我们接受通用对象。这为我们提供了将InvocationHandler
实现重用于不同接口的功能。
动态代理始终需要一个接口。我们不能基于抽象类创建代理。
如果这确实是个大问题,那么可以查看字节码操作库cglib
。cglib
能够通过子类化创建代理,因此能够在不需要接口的情况下为类创建代理。
这里我创建了一个新的User
对象,写了两个方法:
package com.fun.ztest.proxytest;
import com.fun.frame.SourceCode;
public class CUser extends SourceCode {
public void fun() {
output(DEFAULT_STRING);
}
public void tester() {
output(DEFAULT_CHARSET.name());
}
}
下面是代理的实现类:
package com.fun.ztest.proxytest;
import com.fun.frame.SourceCode;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.Arrays;
public class CUserProxy extends SourceCode implements MethodInterceptor {
private static Logger logger = LoggerFactory.getLogger(CUserProxy.class);
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
logger.info("调用方法:{},参数:{}", method.getName(), Arrays.toString(args));
proxy.invokeSuper(obj, args);
return obj;
}
}
下面是使用Demo
:
public static void main(String[] args) {
CUserProxy proxy = new CUserProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CUser.class);
enhancer.setCallback(proxy);
CUser user = (CUser) enhancer.create();
user.fun();
}
INFO-> 当前用户:fv,IP:10.60.131.54,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> 调用方法:fun,参数:[]
INFO-> FunTester
Process finished with exit code 0
代理模式可能非常强大,我们可以添加功能而无需修改实际的代码。代理通常用于向现有类中添加一些通用功能。使用动态代理,我们可以将代理创建与代理实现分开,具备重用性和灵活性多种优点。