代理是指授权代理人在一定范围内代表其向第三方进行处理有关事务。
1 代理模式
需求:1)将业务代码与非业务代码分离,在不改变代码结构的基础上,为其添加新的功能。2)为系统中的某些操作做同一处理,例如进行鉴权、监控、日志、统计等。
1.1 代理模式介绍
增加一个代理对象,在客户端和目标对象之间起到中介作用,去掉客户不能看到的内容和服务,或者添加客户想要的额外服务。
图 代理模式UML
public class StaticProxy {
public static void main(String[] args) {
Subject request = new SimpleRequest();
Subject proxy = new LogProxy(request);
proxy.request("applet","123456");
}
private interface Subject {
void request(String source,String token);
}
private static class LogProxy implements Subject {
private final Subject subject;
private LogProxy(Subject subject) {
this.subject = subject;
}
@Override
public void request(String source, String token) {
System.out.println("记录访问日志,source=" + source + ",token=" + token);
subject.request(source,token);
}
}
private static class SimpleRequest implements Subject {
@Override
public void request(String source, String token) {
System.out.println("接收请求:" + source + "," + token);
}
}
}
代理模式 | 侧重控制对目标对象的访问,及扩展不同于目标对象纬度的功能。 |
装饰模式 | 侧重于增强目标对象本身的功能,是继承方案的一个代替。 |
表 代理模式与装饰模式对比
1.1.1 动态代理
静态代理模式,需要为每个目标对象创建一个代理类,会造成系统中类的数量增多,而且当这些代理类提供的功能相同时,将会造成代码的冗余。动态代理是指在运行时动态创建代理对象。
JDK | JDK自带的一种动态代理方法,要求被代理类必须要实现接口。 原理:动态生成实现目标接口的代理类,调用代理方法时,通过反射的形式来调用目标对象方法。 |
CGLIB | 是CGLIB提供的一种动态代理方法。被代理类可为普通类及接口。 原理:动态生成继承目标类的代理类,通过fastclass 的策略来找到目标方法。 与JDK方式对比,其动态生成的类会比较多,但执行效率会比反射更快。 |
表 Java中两种动态代理
JDK 方式:
public class JdkDynamicProxy {
private interface Subject {
void request(String source,String token);
}
private static class RealRequest implements Subject{
@Override
public void request(String source, String token) {
System.out.println("请求网络");
}
}
private static class RequestIntercept implements InvocationHandler {
private final Subject subject;
private RequestIntercept(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("请求参数:" + Arrays.asList(args));
Object result = method.invoke(subject, args);
System.out.println("记录访问日志");
return result;
}
}
public static void main(String[] args) {
Subject realSubject = new RealRequest();
Subject subject = (Subject) Proxy.newProxyInstance(JdkDynamicProxy.class.getClassLoader(), new Class[]{Subject.class}, new RequestIntercept(realSubject));
subject.request("小程序","1344");
}
}
CGLIB方式
public class CglibDynamicProxy {
private static class RealRequest {
public RealRequest() {
}
public void request(String source, String token) {
System.out.println("请求成功,source=" + source + ",token=" + token);
}
}
private static class RequestIntercept implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("拦截请求:" + Arrays.asList(objects));
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("记录请求日志");
return result;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealRequest.class);
enhancer.setCallback(new RequestIntercept());
RealRequest realRequest = (RealRequest) enhancer.create();
realRequest.request("小程序","1344");
}
}
1.1.2 远程代理
使得客户端可以访问远程机,将网络细节隐藏起来,客户端可完全认为被代理的远程对象是局域的而非远程的。
远程代理对象承担了大部份网络通信工作,并负责对远程业务方法的调用。
RMI,Remote Method Invocation,远程方法调用,是JDK 实现的一种远程代理。
表 RMI 内部实现步骤
客户端通过一个桩(Stub),相当于代理类对象,与远程主机上的业务对象进行通信。而远程主机端有个Skeleton(骨架)对象来负责与Stub对象通信。
远程机部署代码:
public interface RemoteService extends Remote {
String service(String requestInfo) throws RemoteException;
}
public class RemoteServiceImpl extends UnicastRemoteObject implements RemoteService{
public RemoteServiceImpl() throws RemoteException{
}
@Override
public String service(String requestInfo) throws RemoteException {
System.out.println("请求:" + requestInfo);
return "RemoteService提供远程服务:" + requestInfo;
}
}
public class JNDIPublisher {
public static void main(String[] args) throws RemoteException, MalformedURLException {
RemoteService remoteService = new RemoteServiceImpl();
int port = 8080;
String serviceName = "rmi://localhost:" + port + "/remoteService";
LocateRegistry.createRegistry(port);
Naming.rebind(serviceName,remoteService);
}
}
客户端代码:
public class RmiLocal {
public static void main(String[] args) throws MalformedURLException, NotBoundException, RemoteException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String serviceName = "rmi://localhost:" + 8080 + "/remoteService";
reflectRemote(serviceName);
interfaceRemote(serviceName);
}
// 这种方式仍然需要导入远程服务提供的接口,RemoteService
private static void reflectRemote(String serviceName) throws MalformedURLException, NotBoundException, RemoteException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Remote lookup = Naming.lookup(serviceName);
Method method = lookup.getClass().getMethod("service", String.class);
Object result = method.invoke(lookup, "反射方式");
System.out.println("反射方式,远程调用结果:" + result);
}
private static void interfaceRemote(String serviceName) throws MalformedURLException, NotBoundException, RemoteException{
RemoteService remoteService = (RemoteService)Naming.lookup(serviceName);
String result = remoteService.service("接口方式");
System.out.println("接口方式,远程调用结果:" + result);
}
}
1.1.3 虚拟代理
主要目标是延迟对象的创建或者复杂计算,直到它们实际需要被创建或计算。这可以提高性能,降低资源消耗。
public class VirtualAgent {
private interface Image {
void display();
}
private static class RealImage implements Image {
private String path;
public RealImage(String path) {
System.out.println("创建一个图片需要许多资源,耗费时间较长");
this.path = path;
}
@Override
public void display() {
System.out.println("展示图片:" + path);
}
}
private static class ProxyImage implements Image {
private volatile Image image;
private String path;
public ProxyImage(String path) {
System.out.println("虚拟对象,需要资源及时间都很少,用于延迟真实对象的创建与访问");
this.path = path;
}
@Override
public void display() {
if (image == null) {
synchronized (ProxyImage.class) {
if (image == null) image = new RealImage(path);
}
}
image.display();
}
}
public static void main(String[] args) {
Image[] images = new Image[10];
// 在网页加载过程,先加载这些图片,为了防止陷入卡顿,不直接创建图片,而是先创建虚拟对象
System.out.println("网页加载中...");
for (int i = 0; i < images.length; i++) {
images[i] = new ProxyImage(i + ".png");
}
System.out.println("网页加载完成--------");
// 当网页加载完成后,可以依次加载真实图片了
for (Image image : images) {
image.display();
}
}
}
1.1.4 缓存代理
为某些目标操作的结果提供临时的存储空间,让后续相同的操作及相同的参数可以直接共享这些结果,而不必再执行计算,以此来提高系统性能。
public class CacheProxy {
private static class CacheMethodInterceptor implements MethodInterceptor {
private final static Map<MethodKey,Object> resultMap = new HashMap<>();
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
MethodKey methodKey = MethodKey.getKey(method, args);
Object result = resultMap.get(methodKey);
if (result != null) {
return result;
} else {
Object result2 = methodProxy.invokeSuper(o, args);
resultMap.put(methodKey,result2);
return result2;
}
}
}
// 根据method 及 参数来生成key
private static class MethodKey {
private static final Map<String,MethodKey> methodKeyMap = new HashMap<>();
private String code;
private MethodKey(String code) { this.code = code;}
public static MethodKey getKey(Method method, Object[] objects) {
String hasCode = "method:" + method.hashCode();
if (objects != null) hasCode += "args:" + Arrays.hashCode(objects);
MethodKey methodKey = methodKeyMap.get(hasCode);
if (methodKey == null) {
synchronized (MethodKey.class) {
methodKey = new MethodKey(hasCode);
methodKeyMap.put(hasCode,methodKey);
}
}
return methodKey;
}
@Override
public String toString() {
return code;
}
}
private static class ComplexCompute {
public ComplexCompute() {
}
long compute(long num) {
System.out.println("执行复杂计算,参数" + num);
return num * 2345;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ComplexCompute.class);
enhancer.setCallback(new CacheMethodInterceptor());
ComplexCompute compute = (ComplexCompute) enhancer.create();
System.out.println(compute.compute(23));
System.out.println("----------");
System.out.println(compute.compute(123));
System.out.println("----------");
System.out.println(compute.compute(23));
}
}
1.2 优缺点
优点:
- 能动态扩展对象的功能,而不需要修改原有代码,符合开闭原则。
- 能对目标对象的方法访问进行控制与保护。
- 将耗费系统资源及时间较多的对象延迟到真正需要使用时再创建,能提高系统性能及系统对客户的友好度。
- 能调用远程对象方法,而不需要考虑复杂的实现细节。
- 能对运行结果进行缓存,提高系统运行效率。
缺点:
- 增加了代理对象,可能会造成请求的处理速度变慢。
- 增加了类的数量,让系统变得更复杂。