代理,不言而喻就是委托或者替代,而代理者就是委托者或者替代者的意思。代理无处不在,如果说将人的意志和肉体概念上分开,肉体就是意志的代理者,代理意志执行某些事情。为什么会有代理?个人理解,之所以有代理其一是被代理者无法或不想直接完成某些事情,其二,用最低成本获取最高收益。其三,物质世界的必然规律。
今天介绍的这个代理对象,与其说非常重要,倒不如说使用频率极其的高。因为它太重要了。真的无法想象,如果没有代理的存在,我们的代码,甚或软件是否能到现在这样的一种发展程度,未曾可知。不过也无需臆测,因为物质世界的规律就是如此,不可逆转。
代理模式的定义
官方定义:为其他对象提供一种代理以控制对这个对象的访问。
通俗释义:也就是找个替代者。
代理模式也叫:委托模式。是一种非常常用而且实用的设计模式,在很多框架源码中都能看见它的身影,Spring AOP就是代理模式的资深受益者。同时其他的如:状态模式,策略模式,访问者模式其本质也采用了代理模式的思想。
从上面的通用类图可以看出,代理模式有三个角色:
- Subject抽象主题:是一个普通的业务类型定义,可以是接口或者抽象类。
- RealSubject具体主题:被代理或委托的对象,是业务逻辑的具体执行者。
- Proxy代理主题:代理类或者委托类,负责真实主题角色的应用,把所有抽象主题定义的方法委托给真实主题实现,并且在真实主题处理的前后做预处理或者后置处理工作。
代理模式的通用代码
/**
* @Description:普通接口定义(抽象主题类)
*/
public interface Subject {
void request();
}
/**
* @Description:实现抽象主题的具体主题类
*/
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("具体角色业务处理");
}
}
/**
* @Description:代理类
*/
public class Proxy implements Subject{
private Subject subject = null;
//默认被代理者
public Proxy(){
this.subject = new Proxy();
}
//通过构造函数传递代理者
public Proxy(Object...objects){
}
@Override
public void request() {
before();
this.subject.request();
after();
}
private void before(){
System.out.println("前置处理");
}
private void after(){
System.out.println("后置处理");
}
}
上面的before和after方法仅仅是为了做一些前置后置处理,学过SpringAOP应该都比较清楚,可以作为一种面向切面的编程思想。一个代理类可以代理多个被代理者,具体代理哪个具体主题角色由具体业务场景决定,通常情况,一个主题类,一个代理类。也就是一个接口一个代理类,具体代理哪个真是对象,由上层决定,可以采用构造函数方式传入,这种是最简单的清晰的代理模式了,而且在实际的项目中得到广泛的使用。例如:
//代理类的构造函数,由调用层决定具体传入哪个实现类
public Proxy(Subject subject){
this.subject = subject;
}
代理模式的应用场景
代理模式的使用场景简直太多了,这里罗列一下常用的应用场景。
- 访问控制,如果客户端需要访问系统重要的服务对象,那么为了保护后端服务,并进行访问控制,则可以使用代理模式。
- 本地服务访问远程服务,像我们经常翻墙是一样的,需要一个代理服务才可以。
- 日志记录代理,当需要记录操作历史的时候,可以再执行操作之前,请求代理完成相关的记录工作。
代理模式的优缺点
优点:
- 职责清晰。真实角色就是实现实际的业务逻辑,通过代理完成一件事,其结果就是变成简单明了。
- 高扩展性。具体角色随时都有可能变化,只要它实现了接口,不管如何变化,都可以通过代理类完全不做任何修改的情况下使用。
- 智能化。后面介绍的动态代理就可以体现出这一点。
几种常见的代理模式
1. 普通代理
在网络上代理服务器的设置分为透明代理和普通代理,透明代理的意思就是用户不用通过设置代理服务地址,就可以直接访问,也就是说代理服务器对用户来说是透明的,不知道他的存在;而普通代理则是需要用户自己设置代理服务器IP地址,用户必须知道代理的存在。在设计模式中,普通代理就是要知道代理的存在,然后才能访问。
/**
* @Description:普通接口类
*/
public interface IUser {
void eat();
void work();
void sleep();
}
/**
* @Description:真实的码农
*/
public class Coder implements IUser{
private String name;
public Coder(IUser user, String name){
if (user == null){
throw new RuntimeException("没有真是对象");
}else{
this.name = name;
}
}
@Override
public void eat() {
System.out.println(name+":吃饭");
}
@Override
public void work() {
System.out.println(name+":工作");
}
@Override
public void sleep() {
System.out.println(name+":睡觉");
}
}
**
* @Description:码农的私人代理,帮助码农完成一些事情
*/
public class CoderProxy implements IUser{
private IUser user = null;
public CoderProxy(String name){
user = new Coder(this,name);//代理码农,悄悄的躲在这里
}
@Override
public void eat() {
this.user.eat();
}
@Override
public void work() {
this.user.work();
}
@Override
public void sleep() {
this.user.sleep();
}
}
//码农私人代理上线
public static void main(String[] args) {
IUser user = new CoderProxy("张三");
user.eat();
user.work();
user.sleep();
}
以上就是普通代理模式,调用者只知道代理而不知道真是的角色,屏蔽了真是角色的变更对高层模块的影响。在实际项目中,一般都是通过约定来禁止new一个真是的角色,的确是一个非常好的方案。
2. 强制代理
强制代理比较奇葩,一般都是通过代理来找真是的角色,但是强制代理却要强制通过真实角色查找代理角色,否则不能访问。不管是通过代理类或者直接new一个主题角色,都不能访问,只有通过真实角色指定的代理类才能访问,由真实角色管理代理角色,就是这么“豪横”。
/**
* @Author: Max
* @Date: 2020-09-10 22:42
* @Description:普通接口
*/
public interface IUser {
void eat();
void work();
void sleep();
IUser getProxy();//每个人都可以找到自己的代理
}
/**
* @Description:码农自己
*/
public class Coder implements IUser{
private String name;
private IUser proxy;
public Coder(String name){
this.name = name;
}
@Override
public IUser getProxy() {
this.proxy = new CoderProxy(this);//指定代理
return this.proxy;
}
@Override
public void eat() {
if (this.isProxy()){
System.out.println(name+":吃饭");
}else {
System.out.println("无指定代理");
}
}
@Override
public void work() {
if (this.isProxy()){
System.out.println(name+":工作");
}else {
System.out.println("无指定代理");
}
}
@Override
public void sleep() {
if (this.isProxy()){
System.out.println(name+":睡觉");
}else {
System.out.println("无指定代理");
}
}
private boolean isProxy(){//检查是否代理访问
if (this.proxy == null){
return false;
}
return true;
}
}
/**
* @Description:码农代理
*/
public class CoderProxy implements IUser{
private IUser coder;
public CoderProxy(IUser user){
this.coder = user;
}
@Override
public void eat() {
this.coder.eat();
}
@Override
public void work() {
this.coder.work();
}
@Override
public void sleep() {
this.coder.sleep();
}
@Override
public IUser getProxy() {//暂且自己指定自己吧
return this;
}
}
/**
* @Description:模拟调用,只有最后一个可以正常打印。
*/
public class CoderClient {
public static void main(String[] args) {
directReal();
System.out.println("===========");
directProxy();
System.out.println("===========");
mustProxy();
}
//直接访问真实角色
public static void directReal(){
IUser user = new Coder("真实张三");
user.eat();
user.work();
user.sleep();
}
//直接访问代理类
public static void directProxy(){
IUser user = new Coder("张三");
IUser proxy = new CoderProxy(user);
proxy.eat();
proxy.work();
proxy.sleep();
}
//强制代理场景
public static void mustProxy(){
IUser user = new Coder("强制代理张三");
IUser proxy = user.getProxy();
proxy.eat();
proxy.work();
proxy.sleep();
}
}
一个类可以实现多个接口,完成不同任务的整合。代理类不仅仅可以实现主题接口还可以实现其他接口完成不同的任务,代理的目的是在目标对象方法的基础上增强,这种增强的本质其实就是对目标对象方法进行拦截和过滤。当然,一个代理类可以代理多个真实角色,而且这些角色之间可以有一定的耦合。这就是代理模式的扩展性。
3. 动态代理
什么是动态代理?就是在实现的阶段不指定具体的代理对象,而是在运行阶段才指定代理哪个对象。想必了解SpringAOP的童鞋都应该知道这是采用动态代理机制实现的一种面向切面编程的框架。
动态代理的几种实现方式:
1.基于JDK实现动态代理。
这种方式怎么去实现呢?JDK提供了一个Proxy类和InvocationHandler接口,通过实现InvocationHandler接口,然后利用Proxy动态创建代理对象。这里使用的底层技术就是反射。接下来看个简单的例子。
/**
* @Description:对被代理类的方法进行代理
*/
public class UserProxyHandler implements InvocationHandler {
private Object object;
public UserProxyHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法之前");
Object result = method.invoke(this.object,args);//关键的一句代码
System.out.println("调用方法之后");
return result;
}
}
/**
* @Description:动态代理的实际场景
*/
public class ProxyClient {
public static void main(String[] args) {
IUser user = new Person("张三");
InvocationHandler handler = new UserProxyHandler(user);
//获取user的类加载器
ClassLoader classLoader = user.getClass().getClassLoader();
//动态生成代理
IUser proxyUser = (IUser) Proxy.newProxyInstance(classLoader, new Class[]{IUser.class}, handler);
proxyUser.eat();
proxyUser.work();
proxyUser.sleep();
}
}
发现了吧,相比静态代理,我们既没有创建代理类,也没有实现IUser接口,这就是动态代理的神奇之处。要实现动态代理的前提条件是:被代理类必须实现一个接口,Person实现了IUser接口。这也是JDK动态代理的特点代理接口,不过这也是动态代理的不足之处,如果没有实现接口或者接口没有方法是不能进行代理的。
2.基于CGLib实现动态代理。
CGLib是针对类来实现的,为目标对象创建代理对象时,目标对象可以不用实现接口,原理是生成目标对象的子类,覆盖其中的方法实现增强,所以弥补了JDK动态代理的缺陷。
/**
* @Description:模拟服务
*/
public class ProductService {
public Object getProduct(Long id){
System.out.println("获取数据成功,id为"+id);
return "success";
}
}
/**
* @Description:实现cglib的场景类
*/
public class CGlibClient {
public static void main(String[] args) {
ProductService service = new ProductService();
ProductService enhancer = (ProductService) Enhancer.create(service.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object returnValue = "success";
Long param = (Long) objects[0];
if ("getProduct".equals(method.getName())){
returnValue = method.invoke(service,param);
}
return returnValue;
}
});
enhancer.getProduct(5L);
}
}
通过Enhancer
的create方法创建我们的代理对象,其中有两个参数,Class type
,传入的被代理对象的字节码文件,获取了字节码文件就可以获取被代理类的所有信息,Callback callback
实现增强代码的回调,一般通过匿名内部类来实现。这里采用MethodInterceptor
,这个接口实现一个intercept
方法,这个方法有四个参数,Object o
是一个代理对象的引用, Method method
拦截了被代理对象的方法, Object[] objects
获取被代理对象所有的参数, MethodProxy methodProxy
当前执行方法的被代理对象。
除了上述两种实现动态代理的方法之外,还有基于AspectJ实现动态代理和基于Instrumentation实现动态代理,如果有兴趣可以继续深入研究一下。