代理模式介绍
代理模式是23种常用的设计模式之一,其作用是为其他对象提供一种代理来控制对这个对象的访问。主要作用就是加以控制。该模式的好处:在目标对象实现的基础上扩展目标对象的功能。
应用实例理解:
现实中,例如房子出售,买家与卖家无需接触,买房和卖房的所有细节操作都交给中介,其他不用管,这里的中介就是一个代理对象;买火车票不一定非要去火车站才能买,通过火车票代售点也可以买;还有国内是无法直接访问一些外网的,这时我们就可以使用VPN等软件来帮助我们去连接外网,至于连接细节不用关心,只需要上网就行。这里的VPN就起到一个中间层的作用,即代理对象。还有许多例子就不一一说明了。
代理模式特征:
代理类与委托类必须实现同样的接口,代理类主要负责为委托类预处理消息、过滤消息、转发消息给委托类以及事后处理消息等。两者之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类对象本身并不真正实现服务,而是通过调用委托类对象的相关方法来提供特定的服务。
借用网上一张简洁明了的图来说明代理起到什么作用(注:该图是静态代理模式):
代理种类:
代理分为静态代理和动态代理两大类。实现动态代理可以用JDK自带的API来实现,即JDK代理,还有一种就是cglib代理。
- 静态代理:由程序员创建或由特定工具自动生成源代码,再对其编译。程序运行前,代理类的class文件就已存在。
- 动态代理:在程序运行时,运用反射机制动态创建而成。(JDK代理方式)
- 上面仅是对两者作一个简单的区别介绍,下面分别介绍两种代理的使用方式及优缺点。
静态代理
静态代理实例:
/** 代理类和委托类实现的统一接口 */
public interface ImpUserDao {
void saveUser(); //保存用户
}
/** 实现接口的委托类 */
public class UserDao implements ImpUserDao {
@Override
public void saveUser() { //省略User对象,理解就好
System.out.println("保存User对象数据");
}
}
/** 代理类 */
public class ProxyUserDao implements ImpUserDao{
//声明与之关联的委托类对象
private ImpUserDao ud;
//传入实现ImpUserDao接口的类对象,即委托类对象
public ProxyUserDao(ImpUserDao ud) {
this.ud = ud;
}
//拦截UserDao,对其进行控制
@Override
public void saveUser() {
System.out.println("开启事务...");
ud.saveUser(); //执行的是委托类的方法,代理类本身不提供真正实现
System.out.println("提交事务...");
}
}
/** 测试 */
public class Client {
public static void main(String[] args) {
UserDao ud = new UserDao();
ProxyUserDao proxy = new ProxyUserDao(ud);
proxy.saveUser();//执行代理类方法
}
}
静态代理解析:
通过代理类,可以在不影响委托类的原有功能基础上进行扩展,例如开启事务和提交事务功能是委托类所没有的,委托类只需要关心怎么去提交数据,其他事情交给代理类来处理。这样的好处是可以把核心代码和辅助代码分开,而且客户端无需关注实现类(委托类),只需选择代理就行。
静态代理不足之处:
代理类需要和委托类实现同一接口,实现接口方法时还需要把委托类的实现方法放进来,这意味着会出现许多的重复代码,导致代码冗余;每个代理类只能服务于一个类型对象,当每增加一种类型对象时,就需要创建一个对应代理类,随着业务增长,类会越来越多,上层结构一改动,维护会越来越复杂。简要来说就是:静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。 正因为静态代理有以上缺陷,所以需要采用动态代理来解决以上问题。动态代理最简洁一点就是可以代理所有类型对象,这避免创建大量代理类,意味着只需一个代理类就行。
动态代理
Java动态代理的实现需要使用java.lang.reflect包下的InvocationHandler接口和Proxy类。InvocationHandler接口是代理实例调用方法时的调用处理程序,简单来讲就是代理实例做的任何事情都由它来包办,代理类执行任何方法都是在调用该接口的invoke()方法。Proxy类的作用就是提供用于创建动态代理类和实例的静态方法。
invoke()方法参数说明:
/** 参数说明
* proxy :表示动态代理对象
* method : 委托类要执行的方法
* args : 执行方法的参数 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
Proxy类主要方法:
//获取一个代理类Class对象,需要提供关联的类加载器和接口。 形参...表示可以传入多个。
getProxyClass(ClassLoader loader,Class<?>... interfaces)
//获取一个代理类实例,需要提供指定接口的类加载器、一组接口数组和将方法调用指派到指定的调用处理程序。
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
//返回指定动态代理实例的调用处理程序。
getInvocationHandler(Object proxy)
//判断指定的Class对象是否为一个动态代理类
isProxyClass(Class<?> cl)
// 参数说明
// loader : 定义代理类的类加载器
// interfaces : 代理类要实现的接口列表
// h : 指派方法调用的调用处理程序
动态代理实例:
/** 接口 */
interface UserDaoImp{
void saveUser();
}
/** 实现接口的委托类,即目标类 */
class UserDao implements UserDaoImp{
@Override
public void saveUser() {
System.out.println("---执行savaUser()---");
}
}
/** 自定义InvocationHandler 调用处理程序*/
class MyHandler implements InvocationHandler{
private Object tar; //保存目标类对象
public MyHandler(Object tar) {
this.tar = tar;
}
//代理类调用方法时,统一在此处进行处理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---开启事务---");
method.invoke(tar, args); //调用目标类对象的方法
System.out.println("---关闭事务---");
return null;
}
}
//测试
public class Client {
public static void main(String[] args) throws Exception{
//为指定接口生成动态代理类Class字节码对象
Class proClass = Proxy.getProxyClass(UserDaoImp.class.getClassLoader(), UserDaoImp.class);
//获得构造器,参数类型是InvocationHandler.class
Constructor constr = proClass.getConstructor(InvocationHandler.class);
//传入自定义InvocationHandler的实例对象,生成对应的代理类对象
UserDaoImp iud = (UserDaoImp)constr.newInstance( new MyHandler(new UserDao()) );
iud.saveUser();
}
}
//委托类对象
UserDao ud = new UserDao();
UserDaoImp udi = (UserDaoImp) Proxy.newProxyInstance(UserDaoImp.class.getClassLoader(), //加载接口的类加载器
ud.getClass().getInterfaces(), //要代理的一组接口
new MyHandler(ud)); //代理实例的调用处理程序
udi.saveUser();
//输出结果
---开启事务---
---执行savaUser()---
---关闭事务---
interface UserDaoImp {
void saveUser();
}
interface TestImp{
void test();
}
//实现类,实现了以上两个接口
public class UserDao implements UserDaoImp, TestImp{
@Override
public void saveUser() {
System.out.println("执行saveUser()...");
}
@Override
public void test() {
System.out.println("执行test()...");
}
}
//所有代理的统一调用处理程序类
public class MyHandler implements InvocationHandler{
private Object target; //实现类对象
public MyHandler() {}
//传入要代理对象,生成对应代理实例
public Object createProxy(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this); //this:表示该代理实例调用的是当前的调用处理器
}
/** 代理类无论执行什么方法,都会统一调用invoke() */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("打开事务...");
method.invoke(target, args);
System.out.println("关闭事务...");
return null;
}
}
//测试
public class Client {
public static void main(String[] args) {
//委托类对象
UserDao ud = new UserDao();
//获取相应的代理实例
UserDaoImp udi = (UserDaoImp)new MyHandler().createProxy(ud);
TestImp tt = (TestImp)new MyHandler().createProxy(ud);
udi.saveUser();
tt.test();
}
}
//输出结果:
打开事务...
执行saveUser()...
关闭事务...
打开事务...
执行test()...
关闭事务...
动态代理解析:
通过上面的结果来看,如果是采用静态代理来完成,则需要两个代理类,还要保证和类实现的接口是一致的。而通过动态代理,连代理类都不用创建,需要代理时再生成对应的代理实例,不仅逻辑上简洁明了,而且代码量还变得很少;接口中方法都被放在invoke()进行统一处理。但动态代理的缺点也很明显, 要代理的某个类必须实现接口,而生成的代理类也只能代理接口定义的方法,如果是类本身独有的方法就没办法代理。所以可以去使用cglib代理,cglib可以对类进行代理,而不仅限于接口。有兴趣的可以去查找相关资料。以上,就是关于静态代理和动态代理是如何使用的,以及它们的优缺点。