23种设计模式--代理模式(动态与静态)
代理模式
定义:
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。一般我们写代码的时候, 对已经存在的代码尽量不要在修改了, 因为可能好多地方都调用这个方法, 改掉之后可能会出问题, 但是我们可以使用代理对象调用之前的方法进行内容扩充.
意图:
为其他对象提供一种代理以控制对这个对象的访问。
主要解决:
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
代理模式的分类
分为动态代理模式和静态代理模式
入门举例:
静态代理
角色分析:
●抽象角色: 一般会使用接口或者抽象类来解决
●真实角色: 被代理的角色
●代理角色: 代理真实角色,代理真实角色后,我们一-般会做- -些附属操作
●客户: 访问代理对象的人
开发步骤:
接口
package com.gavin.test1;
//租房
public interface Rent {
void rent();
}
真实角色
package com.gavin.test1;
//房东
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要租房子");
}
}
代理角色
package com.gavin.test1;
//代理
public class Proxy implements Rent {
//房东对象
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
heTong();
fare();
}
//看房
public void seeHouse() {
System.out.println("中介带你看房");
}
//签合同
public void heTong() {
System.out.println("签租赁合同");
}
//收中介费
public void fare() {
System.out.println("中介收中介费");
}
}
客户端访问
package com.gavin.test1;
public class Client {
public static void main(String[] args) {
//房东要租房子
Host host = new Host();
//代理,中介帮房东租房子,代理角色一般会有附属操作
Proxy proxy = new Proxy(host);
//客户直接找中介
proxy.rent();
}
}
代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共业务就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
代理模式的缺点:
- 一个真实角色就会产生一个代理角色;代码量会翻倍开发效率会变低~
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
- 静态代理的代码量比较大
静态代理再分析
实际开发中模拟在用户的curd操作上添加日志
代码开发步骤:
接口
package com.gavin.test2;
//用户接口方法
public interface UserService {
public void add( );
public void delete( );
public void update( );
public void query();
}
真实角色
package com.gavin.test2;
//真实对象
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("添加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
代理角色
package com.gavin.test2;
//代理
public class UserServiceProxy implements UserService {
private UserServiceImpl user;
//通过set方法注入
public void setUser(UserServiceImpl user) {
this.user = user;
}
//添加一个日志方法
public void printLog(String msg) {
System.out.println("执行了"+msg+"方法");
}
@Override
public void add() {
printLog("add");
user.add();
}
@Override
public void delete() {
printLog("delete");
user.delete();
}
@Override
public void update() {
printLog("update");
user.update();
}
@Override
public void query() {
printLog("query");
user.query();
}
}
测试
package com.gavin.test2;
public class Client {
public static void main(String[] args) {
UserServiceImpl userServiceImpl = new UserServiceImpl();
//userServiceImpl.add();
//在不修改原有业务代码的基础上,横切到业务中,做业务增强
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUser(userServiceImpl);
proxy.add();//执行带有输出日志的方法
proxy.delete();
proxy.query();
proxy.update();
}
}
结果
执行了add方法
添加了一个用户
执行了delete方法
删除了一个用户
执行了query方法
查询了一个用户
执行了update方法
修改了一个用户
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
基于接口的动态代理
基于子类的动态代理
基于接口的动态代理:
涉及的类: Proxy
提供者: JDK官方
如何创建代理对象:
使用Proxy类中的newProxyInstance方法
创建代理对象的要求:
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:
ClassLoader:类加载器
它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
Class[]:字节码数组
它是用于让代理对象和被代理对象有相同方法。固定写法。
InvocationHandler:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类, 通常情况下都是匿名内部类,但不是必须的。
测试案例:已用户的增删改查为例,通过在执行该操作前打印日志信息
接口
package com.gavin.test3;
//用户接口方法
public interface UserService {
public void add( );
public void delete( );
public void update( );
public void query();
}
实现类
package com.gavin.test3;
//真实对象
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("添加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
代理类
该类必须要实现InvocationHandler接口,重写invoke方法,在不改变原有业务的基础上实现方法增强
package com.gavin.test3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 target 这个对象上
*/
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
* 该方法处理代理实例,并返回结果
* proxy 代理对象的引用
* method 当前执行的方法
* args 当前执行的方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行方法以前执行
printLog(method.getName());
Object result = method.invoke(target, args);
//执行方法后执行
System.out.println("你成功"+method.getName()+"了一个用户");
return result;
}
//添加打印日志的方法
public void printLog(String msg) {
System.out.println("执行了"+msg+"方法");
}
}
客户端
package com.gavin.test3;
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userServiceImpl = new UserServiceImpl();
//代理角色,是不存在的
ProxyInvocationHandler pHandler = new ProxyInvocationHandler();
//设置要代理的对象
pHandler.setTarget(userServiceImpl);
//动态生成代理类
UserService userService = (UserService) pHandler.getProxy();
//执行方法
userService.add();
}
}
测试结果
执行了add方法
添加了一个用户
基于子类的动态代理:
涉及的类: Enhancer
提供者:第三方cglib库
如何创建代理对象:
使用Enhancer类中的create方法
创建代理对象的要求:
被代理类不能是最终类
create方法的参数:
Class:字节码
它是用于指定被代理对象的字节码。
Callback:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类: MethodInterceptor
优点
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。在本示例中看不出来,因为invoke方法体内嵌入了具体的外围业务(记录任务处理前后时间并计算时间差),实际中可以类似Spring AOP那样配置外围业务。
缺点
诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。