代理模式在java中扮演着很重要的角色,我们常用的spring、mybatis中都是用了代理模式,学习代理模式能让我们更好的使用和了解这些框架。
代理模式概念
代理模式:给目标对象提供一个代理对象,由代理对象控制目标对象的访问。
使用代理模式的好处:
1、通过引入代理对象来间接访问目标对象,防止直接访问目标对象对系统造成不必要的复杂性。
2、通过代理对象对原有业务进行增强。
设计原则
在看例子之前先了解一下设计原则:
- 单一职责原则: 一个类或者一个接口只负责唯一项职责,尽量设计出功能单一的接口;
- 依赖倒转原则:高层模块不应该依赖低层模块具体实现,解耦高层与低层。 既面向接口编程, 当实现发生变化时, 只需提供新的实现类, 不需要修改高层模块代码;
- 开放-封闭原则: 程序对外扩展开放, 对修改关闭; 换句话说, 当需求发生变化时,我们可以通过添加新模块来满足新需求, 而不是通过修改原来的实现代码来满足新需求;
- 迪米特法则: 一个对象应该对其他对象保持最少的了解, 尽量降低类与类之间的耦合度; 实现这个原则, 要注意两个点, 一方面在做类结构设计的时候尽量降低成员的访问权限, 能用 private 的尽量用 private; 另外在类之间, 如果没有必要直接调用, 就不要有依赖关系; 这个法则强调的还是类之间的松耦合;
- 里氏代换原则: 所有引用基类(父类) 的地方必须能透明地使用其子类的对象;
- 接口隔离原则: 客户端不应该依赖它不需要的接口, 一个类对另一个类的依赖应该建立在最小的接口上;
静态代理
代理对象和目标对象要实现相同的接口。
例子:
现在你需要买双篮球鞋,然而国内买不到,刚好你有个朋友叫戴立,他在做代购,可以帮你买到,然后你就通过他来代购。代码如下:
ShoeFactory.java
/**
* 代理对象和真实对象要实现的公共接口
* @author Aaron 公众号:码农路
* @Date 2020/3/8 17:32
**/
public interface ShoeFactory {
// 销售鞋子
void saleShoe();
}
ShoeShop.java
/**
* 鞋店(真实对象)
* @author Aaron 公众号:码农路
* @Date 2020/3/8 17:30
**/
public class ShoeShop implements ShoeFactory{
@Override
public void saleShoe() {
System.out.println("买双篮球鞋.");
}
}
BuyingAgent.java
/**
* 代购类(代理对象)
* @author Aaron 公众号:码农路
* @Date 2020/3/8 17:49
**/
public class BuyingAgent implements ShoeFactory{
private ShoeFactory shoeFactory;
public BuyingAgent(ShoeFactory shoeFactory) {
this.shoeFactory = shoeFactory;
}
@Override
public void saleShoe() {
beforeAdvice();
shoeFactory.saleShoe();
afterAdvice();
}
private void beforeAdvice(){
System.out.println("询问需要.");
}
private void afterAdvice(){
System.out.println("收取手续费.");
}
}
测试类:
public class StatTest {
public static void main(String[] args) {
BuyingAgent agent = new BuyingAgent(new ShoeShop());
agent.saleShoe();
}
}
结果:
询问需要.
买双篮球鞋.
收取手续费.
上述是一个简单的静态代理例子。
假如有一天你有了女朋友(只是假如 笑哭 哭笑 笑出眼泪 破涕为笑 笑死 笑尿 笑cry),她想买支口红,国内暂时也没有卖,她又迫切的想要,你不得不满足她,你又通过你的代购朋友戴立来帮忙购买。
这时就要增加一个公共接口和一个真实对象,代理对象也要实现公共接口
代码如下:
LipstickFactory.java
/**
* 代理对象和真实对象要实现的公共接口
* * @author Aaron 公众号:码农路
* @Date 2020/3/8 18:17
**/
public interface LipstickFactory {
void saleLipstick();
}
LipStickShop.java
/**
* 口红店(真实对象)
* * @author Aaron 公众号:码农路
* @Date 2020/3/8 18:18
**/
public class LipStickShop implements LipstickFactory {
@Override
public void saleLipstick() {
System.out.println("卖口红");
}
}
BuyingAgent.java
/**
* 代购类(代理对象)
* @author Aaron 公众号:码农路
* @Date 2020/3/8 17:49
**/
public class BuyingAgent implements ShoeFactory , LipstickFactory{
private ShoeFactory shoeFactory;
private LipstickFactory lipstickFactory;
public BuyingAgent(ShoeFactory shoeFactory) {
this.shoeFactory = shoeFactory;
}
public BuyingAgent(LipstickFactory lipstickFactory) {
this.lipstickFactory = lipstickFactory;
}
@Override
public void saleShoe() {
beforeAdvice();
shoeFactory.saleShoe();
afterAdvice();
}
@Override
public void saleLipstick() {
beforeAdvice();
lipstickFactory.saleLipstick();
afterAdvice();
}
private void beforeAdvice(){
System.out.println("询问需要.");
}
private void afterAdvice(){
System.out.println("收取手续费.");
}
}
测试类:
public class StatTest {
public static void main(String[] args) {
ShoeShop shoeShop = new ShoeShop();
BuyingAgent agent = new BuyingAgent(shoeShop);
agent.saleShoe();
System.out.println("------------------");
LipStickShop lipStickShop = new LipStickShop();
agent = new BuyingAgent(lipStickShop);
agent.saleLipstick();
}
}
结果如下:
询问需要.
买双篮球鞋.
收取手续费.
------------------
询问需要.
卖口红
收取手续费.
我们发现使用静态代理时,每次增加新的需求时,都要再增加一个公共接口、一个真实对象,同时代理对象也要实现公共接口,这样就违反了开闭原则,理想的情况下我们是想在增加新需求的时候不会改动之前的模块而是在之前模块的基础上来完成新的需求的,显然静态代理是做不到的,静态代理违反了开闭原则。这时就引申出来了动态代理。
动态代理
在了解动态代理之前要先简单的了解InvocationHandler接口和Proxy类。
1、InvocationHandler接口
InvocationHandler 是一个代理实例要实现的接口,每个代理实例都关联着一个invocation handler,当代理实例的方法调用时,方法的调用会被编码并转发到invocation handler 的invoke方法的调用。
这是官方的解释,有兴趣的话可以看一下
InvocationHandler接口 只有一个invoke方法,它处理代理实例方法的调用并返回结果。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
2、Proxy 类
Proxy 提供了创建动态代理的静态方法类和实例。Proxy 通过newProxyInstance静态方法与InvocationHandler 绑定在一起并生成代理对象实例返回。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
了解完之后言归正传,还是上面那个例子,如果需求变动戴立又需要代购其他东西,如果每个真实对象都要往代理对象注册的话会造成代理类越来越庞大,使用动态代理,每次增加需求不需要更改代理对象,只需要增加公共接口和真实对象即可。
例子:
DynamicBuyingAgent.java
**
* 代购类(代理对象)
* @author Aaron 公众号:码农路
* @Date 2020/3/11 22:02
**/
public class DynamicBuyingAgent implements InvocationHandler {
// 真实对象
private Object factory;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeAdvice();
Object invoke = method.invoke(factory , args);
afterAdvice();
return invoke;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(factory.getClass().getClassLoader() , factory.getClass().getInterfaces() , this);
}
private void beforeAdvice(){
System.out.println("询问需要.");
}
private void afterAdvice(){
System.out.println("收取手续费.");
}
public Object getFactory() {
return factory;
}
public void setFactory(Object factory) {
this.factory = factory;
}
}
factory 类型是Object 是因为代理对象只做一件事,它所做的事情就一件,所以可以代理所有真实对象。
测试类:
public class DynamicTest {
public static void main(String[] args) {
DynamicBuyingAgent dynamicBuyingAgent = new DynamicBuyingAgent();
// 代理鞋
DynamicShoeShop dynamicShoeShop = new DynamicShoeShop();
dynamicBuyingAgent.setFactory(dynamicShoeShop);
DynamicShoeFactory shoeFactory = (DynamicShoeFactory) dynamicBuyingAgent.getProxyInstance();
shoeFactory.saleShoe("篮球鞋");
System.out.println("-------------------------");
// 代理口红
LipStickShop lipStickShop = new LipStickShop();
dynamicBuyingAgent.setFactory(lipStickShop);
LipstickFactory lipstickFactory = (LipstickFactory) dynamicBuyingAgent.getProxyInstance();
lipstickFactory.saleLipstick();
}
}
结果:
询问需要.
买双篮球鞋鞋.
收取手续费.
-------------------------
询问需要.
卖口红
收取手续费.
公共接口和真实对象使用的还是静态代理的,每次增加新的真实对象就可以不更改代理对象,只需增加公共接口和真实对象。
静态代理和动态代理的区别
1、静态代理在编译期就已经实现,编译完成后会生成一个class文件。
2、动态代理是在运行时自动生成的,即编译后没有实际的class文件而是在运行时动态生成类的字节码,并加载到JVM中。
3、动态代理的代理对象不需要实现接口,但是真是对象必须实现接口,否则不能使用动态代理。