设计模式十四之代理模式
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
1. 模式的定义与特点
1.1 模式的定义
代理模式(Proxy):由于某些原因需要给某些对象提供一个代理以控制对该对象的访问,这时访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
1.2 模式的特点
代理模式的优点有:
1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2. 代理对象可以扩展目标对象的功能;
3. 代理模式将客户端和目标对象分离,在一定程度上降低了系统的耦合度。
代理模式的缺点有:
1. 在客户端与目标对象之间增加一个代理对象,会造成请求处理速度变慢;
2. 增加了系统的复杂度。
1.3 模式的使用场景
1. 保护目标对象;
2. 增强目标对象。
2. 模式的结构与实现
2.1 模式的结构
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,代理模式的主要角色如下:
1. 抽象主题类(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法;
2. 真实主题类(Real Subject):实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引入的对象
3. 代理类(Proxy):提供了与真实主题相同的接口,其内部包含了对真实主题的引用,它可以访问,控制和扩展真实主题的功能。
按照代理的创建时期,代理类可以分为两种:
1. 静态代理:由程序员创建或特定工具自动生成源代码,在对其编译,在程序运行前,代理类的.class文件就已经存在;
2. 动态代理:在程序运行时,运用反射机制动态创建。
2.2 模式的实现
2.2.1 静态代理
抽象主题
/**
* 抽象主题 - 账户类
*/
public interface AccountService {
void queryAccount();
}
具体主题
/**
* 真实主题 - 账户类
*/
public class AccountServiceImpl implements AccountService {
@Override
public void queryAccount() {
System.out.println("查看账户信息方法...");
}
}
代理类
/**
* 代理类
*/
public class AccountStaticProxy implements AccountService {
private AccountService accountService;
public AccountProxy(AccountService accountService) {
this.accountService = accountService;
}
@Override
public void queryAccount() {
beforeMethod();
accountService.queryAccount();
afterMethod();
}
private void beforeMethod() {
System.out.println("方法执行前处理...");
}
private void afterMethod() {
System.out.println("方法执行后处理...");
}
}
客户端
public class Client {
public static void main(String[] args) {
AccountService accountService = new AccountServiceImpl();
AccountStaticProxy accountProxy = new AccountStaticProxy(accountService);
accountProxy.queryAccount();
}
}
# 运行结果如下:
方法执行前处理...
查看账户信息方法...
方法执行后处理...
观察代码可以发现每一个代理类都只能为一个接口服务,这样程序开发中必然会产生过多的代理,而且所有的代理类除了调用的方法不一样,其他的操作都一样,此时肯定都是重复代码,解决方法是通过一个代理类完成所有的代理功能,那么此时就必须使用动态代理。
2.2.2 动态代理
与静态代理类相比,动态代理类的字节码是在程序运行时由 Java 反射机制动态生成,无需程序员手动编写其源代码,动态代理类不仅简化了编程工作,而且提高了系统的扩展性,因为 Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口提供了生成动态代理类的能力。
JDK 动态代理包含一个类(Proxy)和一个接口(InvocationHandler),可以将 InvocationHandler 接口的子类想象成一个代理的最终操作类
/*
* @param proxy:指被代理的对象
* @param method:要调用的方法
* @param args:方法调用时所需要的参数
*/
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}
而 Proxy 类是专门完成代理的操作类,可以通过此类为一个或多个接口动态的生成实现类,此类提供了如下的操作方法:
/*
* @param loader:类加载器
* @param interfaces:得到全部的接口
* @param h:得到InvocationHandler接口的子类实例
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在 Java中主要有一下三种类加载器:
1. Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;
2. Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类;
3. AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。
抽象主题
/**
* 抽象主题 - 账户类
*/
public interface AccountService {
void queryAccount();
}
具体主题
/**
* 真实主题 - 账户类
*/
public class AccountServiceImpl implements AccountService {
@Override
public void queryAccount() {
System.out.println("查看账户信息方法...");
}
}
代理类
/**
* 代理类
*/
public class AccountDynamicProxy implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
*
* @param proxy 被代理的对象
* @param method 要调用的方法
* @param args 要调用方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeMethod();
Object result = method.invoke(target, args);
afterMethod();
return result;
}
private void beforeMethod() {
System.out.println("方法执行前处理...");
}
private void afterMethod() {
System.out.println("方法执行后处理...");
}
}
客户端
public class client {
public static void main(String[] args) {
AccountService accountService = (AccountService) new AccountDynamicProxy().bind(new AccountServiceImpl());
accountService.queryAccount();
}
}
# 运行结果如下:
方法执行前处理...
查看账户信息方法...
方法执行后处理...
JKD 的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用 JDK 动态代理,这时就要使用 Cglib 动态代理了。
2.2.3 Cglib 动态代理
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
具体主题
/**
* 具体主题
*/
public class AccountService {
public void queryAccount() {
System.out.println("查询账户信息方法...");
}
}
代理类
public class AccountCglibProxy implements MethodInterceptor {
private Object target;
public Object bind(Object target) {
this.target = target;
// 创建Enhancer对象,类似于JDK动态代理的Proxy类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
/**
*
* @param o cglib生成的代理对象
* @param method 被代理对象方法
* @param objects 方法入参
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
beforeMethod();
Object result = methodProxy.invokeSuper(o, objects);
afterMethod();
return result;
}
private void beforeMethod() {
System.out.println("方法执行前处理...");
}
private void afterMethod() {
System.out.println("方法执行后处理...");
}
}
客户端
public class Client {
public static void main(String[] args) {
AccountService accountService = (AccountService) new AccountCglibProxy().bind(new AccountService());
accountService.queryAccount();
}
}
# 运行结果如下:
方法执行前处理...
查询账户信息方法...
方法执行后处理...
3. 模式在开源软件中的应用
3.1 Spring.AOP
切面在应用有运行的时刻被织入,一般情况下,在织入切面时,AOP 容器会为目标对象动态创建一个代理对象,Spring 中的代理模式在 AOP 中的体现,比如 JDKDynamicAopProxy 和 CglibAopProxy。
3.2 Mybatis 中的应用
代理模式可以认为是 Mybatis 的核心使用的模式,正是由于这个模式,我们才可以只要编写 Mapper.java 接口,不需要实现,由 Mybatis 后台帮我们完成具体的 SQL 的执行。