1.目标
随着对spring框架的了解,代理模式在其中发乎了很大作用,要想更好的理解spring的核心之一AOP需要对代理模式有一些了解。通过本本能够更好的理解代理模式设计思想、应用实例、优缺点、与适配器模式的不同、JDK动态代理、Cglib动态代理。
2.介绍
2.1 概述
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。
一句话总结: 为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。
2.2 代理模式详细理解
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
2.3 实际用途
1、Windows 里面的快捷方式。
2、买火车票不一定在火车站买,也可以去代售点。
3、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
4、spring aop。
2.4 代理模式优缺点
- 优点:
- 1、职责清晰。
- 2、高扩展性。
- 3、智能化。
缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
2.5 按使用场景分类
使用场景:按职责来划分,通常有以下使用场景:
- 1、远程代理。
- 2、虚拟代理。
- 3、Copy-on-Write 代理。
- 4、保护(Protect or Access)代理。
- 5、Cache代理。
- 6、防火墙(Firewall)代理。
- 7、同步化(Synchronization)代理。
- 8、智能引用(Smart Reference)代理。
2.6 与适配器模式和装饰器模式的不同
注意事项:
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
适配器模式是因为新旧接口不一致导致出现了客户端无法得到满足的问题,因为我们还想使用实现了这个接口的一些服务,旧的接口是不能被完全重构掉。故:那么为了使用以前实现旧接口的服务,我们就应该把新的接口转换成旧接口;相比于适配器的应用场景,代理就不一样了,虽然代理也同样是增加了一层,但是, 代理提供的接口和原本的接口是一样的 ,代理模式的作用是不把实现直接暴露给client,而是通过代理这个层,代理能够做一些处理;
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
3.静态代理
静态代理: 代理和被代理对象在代理之前是确定的。他们都实现相同的接口或者继承相同的抽象类
3.1 静态代理简单实现
以汽车行驶,记录汽车行驶时间为例子进行讲解,首先创建一个Movable接口。1
2
3
4
5package com.rocklei123.designpatterns.proxypattern.staticproxy;
public interface MoveAble {
void move();
}
创建一个Car实现MoveAble接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.rocklei123.designpatterns.proxypattern.staticproxy;
import java.util.Random;
/**
* @ClassName: Car
* @Author: rocklei123
* @Date: 2018/10/28 23:35
* @Description: TODO
* @Version 1.0
*/
public class Car implements MoveAble {
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶。。。"); //实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
long endtime = System.currentTimeMillis();
System.out.println("汽车结束行驶。。。 汽车行驶时间:" + (endtime - starttime) + "毫秒!");
}
}
创建一个测试Client类用户测试1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.rocklei123.designpatterns.proxypattern.staticproxy;
/**
* @ClassName: Client
* @Author: rocklei123
* @Date: 2018/10/28 23:38
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Car car = new Car();
car.move();
}
}
测试结果1
2
3汽车开始行驶。。。
汽车行驶中。。。
汽车结束行驶。。。 汽车行驶时间:717毫秒!
3.2 通过继承实现代理
创建一个ExtendsCarForProxy继承Car,然后删除Car里面有关计时的代码,放到ExtendsCarForProxy里面。
修改Car类代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.rocklei123.designpatterns.proxypattern.staticproxy;
import java.util.Random;
/**
* @ClassName: Car
* @Author: rocklei123
* @Date: 2018/10/28 23:35
* @Description: TODO
* @Version 1.0
*/
public class Car implements MoveAble {
public void move() {
/* long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶。。。"); //实现开车*/
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
/*long endtime = System.currentTimeMillis();
System.out.println("汽车结束行驶。。。 汽车行驶时间:" + (endtime - starttime) + "毫秒!");*/
}
}
创建ExtendsCarForProxy集成Car 类,重写move方法
1 | package com.rocklei123.designpatterns.proxypattern.staticproxy; |
修改Client类代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.rocklei123.designpatterns.proxypattern.staticproxy;
/**
* @ClassName: Client
* @Author: rocklei123
* @Date: 2018/10/28 23:38
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
//Car car = new Car();
MoveAble m = new ExtendsCarForProxy();
m.move();
}
}
通过继承实现代理结果输出
1 | ExtendsCarForProxy 汽车开始行驶。。。 |
3.3 使用聚合方式
创建聚合方式的Car类AggregationCarForProxy,实现MoveAble接口,并创建有参构造方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.rocklei123.designpatterns.proxypattern.staticproxy;
/**
* @ClassName: AggregationCarForProxy
* @Author: rocklei123
* @Date: 2018/10/28 23:51
* @Description: TODO
* @Version 1.0
*/
public class AggregationCarForProxy implements MoveAble {
private Car car;
public AggregationCarForProxy(Car car) {
super();
this.car = car;
}
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("AggregationCarForProxy 汽车开始行驶。。。");
car.move();
long endtime = System.currentTimeMillis();
System.out.println("AggregationCarForProxy 汽车结束行驶。。。 汽车行驶时间:" + (endtime - starttime) + "毫秒!");
}
}
修改Client类代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.rocklei123.designpatterns.proxypattern.staticproxy;
/**
* @ClassName: Client
* @Author: rocklei123
* @Date: 2018/10/28 23:38
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
/* Car car = new Car();
MoveAble m = new ExtendsCarForProxy();
m.move();*/
Car car = new Car();
MoveAble m = new AggregationCarForProxy(car);
m.move();
}
}
聚合类型执行结果1
2
3AggregationCarForProxy 汽车开始行驶。。。
汽车行驶中。。。
AggregationCarForProxy 汽车结束行驶。。。 汽车行驶时间:764毫秒!
聚合方式就是两个类实现同一个接口,Car类实现move功能,然后通过构造函数把Car类注入到AggregationCarForProxy中,在调用Car类move时做一些处理,从而实现了代理功能。
3.4 聚合方式优点体现
问题
如果我们需要在Move上实现时间处理、权限、日志等功能。比如需要实现先输出时间,再输出日志就需要创建一个Car4继承Car,如果我想先输出日志,再输出时间呢?就需要在创建一个Car5继承Car,只是顺序颠倒一下,就需要在创建一个,如果功能多的话,就需要创建很多类,太麻烦了。使用聚合就不会有这种问题出现。
聚合方式创建多个代理类,根据需求顺序调用
创建时间代理类,专门处理时间1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.rocklei123.designpatterns.proxypattern.staticproxy;
/**
* @ClassName: AggregationCarTimeStaticProxy
* @Author: rocklei123
* @Date: 2018/10/29 00:05
* @Description: TODO
* @Version 1.0
*/
public class AggregationCarTimeStaticProxy implements MoveAble {
private MoveAble m;
public AggregationCarTimeStaticProxy(MoveAble m) {
super();
this.m = m;
}
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("AggregationCarTimeStaticProxy 汽车开始行驶。。。");
m.move();
long endtime = System.currentTimeMillis();
System.out.println("AggregationCarTimeStaticProxy 汽车结束行驶。。。 汽车行驶时间:" + (endtime - starttime) + "毫秒!");
}
}
创建专门处理日志的代理类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.rocklei123.designpatterns.proxypattern.staticproxy;
/**
* @ClassName: AggregationCarLogStaticProxy
* @Author: rocklei123
* @Date: 2018/10/29 00:06
* @Description: TODO
* @Version 1.0
*/
public class AggregationCarLogStaticProxy implements MoveAble {
private MoveAble m;
public AggregationCarLogStaticProxy(MoveAble m) {
super();
this.m = m;
}
public void move() {
System.out.println("AggregationCarLogStaticProxy 开始日志记录。。。");
m.move();
System.out.println("AggregationCarLogStaticProxy 日志记录结束。。。");
}
}
修改Client类代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.rocklei123.designpatterns.proxypattern.staticproxy;
/**
* @ClassName: Client
* @Auther: rocklei123
* @Date: 2018/10/28 23:38
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
/* Car car = new Car();
MoveAble m = new ExtendsCarForProxy();
m.move();*/
/*Car car = new Car();
MoveAble m = new AggregationCarForProxy(car);
m.move();*/
Car car = new Car();
MoveAble ctp = new AggregationCarTimeStaticProxy(car);//执行时间
MoveAble clp = new AggregationCarLogStaticProxy(ctp);//记录日志
clp.move();
}
}
执行结果1
2
3
4
5AggregationCarLogStaticProxy 开始日志记录。。。
AggregationCarTimeStaticProxy 汽车开始行驶。。。
汽车行驶中。。。
AggregationCarTimeStaticProxy 汽车结束行驶。。。 汽车行驶时间:881毫秒!
AggregationCarLogStaticProxy 日志记录结束。。。
3.5 静态代理类优缺点总结
- 优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)。
- 缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。
引入下文,动态代理
4.Java动态代理
书接上文,现在只是对小汽车进行代理,如果要实现对火车,自行车的代理是不是需要创建火车代理类,自行车代理类等,太麻烦了。可以通过动态代理进行实现。
4.1 Java动态代理概念
Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。
4.2 JDK动态代理
JDK自从1.3版本开始,就引入了动态代理,JDK的动态代理用起来非常简单,但是它有一个限制,就是使用动态代理的对象必须实现一个或多个接口 。如果想代理没有实现接口的类可以使用CGLIB包。
首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:
- 1、通过实现 InvocationHandler 接口创建自己的调用处理器;
- 2、通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 3、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
自定义自己的InvocationHandler1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36package com.rocklei123.designpatterns.proxypattern.dynamic.jdkdynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @ClassName: TimeHadlerJdkDynamicProxy
* @Author: rocklei123
* @Date: 2018/10/29 11:22
* @Description: TODO
* @Version 1.0
*/
public class TimeHadlerJdkDynamicProxy implements InvocationHandler {
private Object target;
public TimeHadlerJdkDynamicProxy(Object target) {
this.target = target;
}
/**
* @param proxy proxy 被代理对象
* @param method 被代理对象的方法
* @param args 被代理对象方法的参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long starttime = System.currentTimeMillis();
System.out.println("TimeHadlerJdkDynamicProxy 汽车开始行驶。。。");
method.invoke(target);
long endtime = System.currentTimeMillis();
System.out.println("TimeHadlerJdkDynamicProxy 汽车结束行驶。。。 汽车行驶时间:" + (endtime - starttime) + "毫秒!");
return null;
}
}
客户端调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29package com.rocklei123.designpatterns.proxypattern.dynamic.jdkdynamicproxy;
import com.rocklei123.designpatterns.proxypattern.staticproxy.Car;
import com.rocklei123.designpatterns.proxypattern.staticproxy.MoveAble;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @ClassName: Client
* @Author: rocklei123
* @Date: 2018/10/29 11:27
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Car car = new Car();
InvocationHandler h = new TimeHadlerJdkDynamicProxy(car);
Class<?> clazz = car.getClass();
/**
* loader 类加载器
* interfaces 实现接口
* h InvocationHandler
* */
MoveAble m = (MoveAble) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), h);
m.move();
}
}
执行效果1
2
3TimeHadlerJdkDynamicProxy 汽车开始行驶。。。
汽车行驶中。。。
TimeHadlerJdkDynamicProxy 汽车结束行驶。。。 汽车行驶时间:524毫秒!
4.2 Cglib动态代理
CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理.
cglib有两种可选方式,继承和引用。第一种是基于继承实现的动态代理,所以可以直接通过super调用target方法,但是这种方式在spring中是不支持的,因为这样的话,这个target对象就不能被spring所管理,所以cglib还是才用类似jdk的方式,通过持有target对象来达到拦截方法的效果。
注意: CGLIB不能对final修饰的类进行代理。
CGLIB的核心类:
* net.sf.cglib.proxy.Enhancer – 主要的增强类
* net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
* net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
* Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。
Maven 构建需要的jar包1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
准备被代理类1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.rocklei123.designpatterns.proxypattern.dynamic.cglibdynamicproxy;
/**
* @ClassName: Train
* @Author: rocklei123
* @Date: 2018/10/29 12:30
* @Description: TODO
* @Version 1.0
*/
public class Train {
public void move() {
System.out.println("火车行驶中。。。");
}
}
cglib实现动态代理
1 | package com.rocklei123.designpatterns.proxypattern.dynamic.cglibdynamicproxy; |
客户端测试类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.rocklei123.designpatterns.proxypattern.dynamic.cglibdynamicproxy;
/**
* @ClassName: Client
* @Author: rocklei123
* @Date: 2018/10/29 13:05
* @Description: TODO
* @Version 1.0
*/
public class TrainClient {
public static void main(String[] args) {
TrainTimerCglibDynamicProxy proxy = new TrainTimerCglibDynamicProxy();
Train train = (Train) proxy.getProxy(Train.class);
train.move();
}
}
测试结果
1 | TrainTimerCglibDynamicProxy 火车开始行驶。。。 |
4.3 JDK动态代理和Cglib动态代理的区别
JDK动态代理 | CGLIB动态代理 |
---|---|
只能代理实现了接口的类 | 针对类来实现代理的 |
没有实现接口的类不能实现JDK的动态代理 | 对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用 |