文章目录
- 八、代理设计模式
- 8.1 结构型设计模式简介
- 8.2 代理设计模式简介
- 8.2.1 代理设计模式概述
- 8.2.2 代理设计模式的UML类图
- 8.3 代理设计模式的实现
- 8.3.1 静态代理和动态代理简介
- 8.3.2 静态代理的实现
- 8.3.3 动态代理的实现
- 1)JDK动态代理
- 2)CGLIB代理
- 8.3.4 JDK代理和GBLIB代理的区别
- 8.4 代理设计模式优缺点
八、代理设计模式
8.1 结构型设计模式简介
结构型模式(Structural Pattern)关注类和对象的组合。其描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
结构型模式可以分为类结构型模式和对象结构型模式:
- 1)类结构型模式:类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承和实现关系。
- 2)对象结构型模式:对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性,因此大部分结构型模式都是对象结构型模式。
8.2 代理设计模式简介
8.2.1 代理设计模式概述
代理设计模式(Proxy Pattern):指为其他对象提供一种代理,以控制这个对象的访问;
由于某种原因,一个对象不能够直接引用另一个对象时,代理对象可起到一个中介作用。即用代理对象来调用目标对象(被代理对象,资源的真实拥有者),并且代理对象也可以在目标对象的实现基础上进行增强,另外代理对象可以控制目标对象的访问,进行安全控制;
Tips:代理模式主要的功能就是对类进行增强,并且对目标对象进行安全控制;
生活中的代理有很多场景:
调用者(客户端) | 代理角色 | 真实角色(目标对象) |
买电脑的人 | 电脑代理商 | 生产电脑的厂商 |
买火车票 | 黄牛 | 火车站,12306 |
租房子 | 房子中介 | 房东 |
我们发现代理角色和真实角色都具有相同的功能(卖电脑/卖票),代理商可以在中间赚取差价(修改原有的功能)。
8.2.2 代理设计模式的UML类图
代理设计模式中一般包含3个角色:
- 1)抽象主题(Subject):用于规范真实角色和代理角色拥有共同的业务方法;
- 2)真实主题(RealSubject):实现了抽象主题中的具体业务,提供业务的和兴功能;也叫目标对象
- 3)代理主题(Proxy):实现了真实主题所实现的所有接口,确保与真实主题拥有同样的方法,即保证真实主题的所有方法都能代理到,器内部含有真实主题的引用,可以调用真实主题的核心方法;可以在调用真实主题的方法前后进行功能的增强,也可以进行真实主题的方法访问控制;
8.3 代理设计模式的实现
8.3.1 静态代理和动态代理简介
代理模式分为静态代理和动态代理。
- 静态代理:静态代理类在编译期就生成,在不修改源代码的情况下,静态代理类是不会发生改变的,当真实对象扩展了一个方法时,必须要手动的修改静态代理类来确保能够代理到真实对象最新的方法;
- 动态代理:动态代理类是在Java运行时动态生成的,当真实对象扩展任意方法时,动态代理类也会自动的扩展方法,不需要我们修改源代码;在Java中,动态代理分为JDK动态代理和CGLIB动态代理;
【案例】设计一套租房程序:
如果我们要去租房,需要去找房东,但是房东很可能不在家,但是房东会把房子交给中介,我们可以去中介寻找合适的房源;这就是一个典型的代理模式,目标对象是房东,房子是在房东手里的,中介是代理对象,对目标对象进行增强(我们在中介手里租的房子肯定比房东收的价格要贵);
8.3.2 静态代理的实现
- 抽象主题:
package com.pattern.demo01;
/**
* @author lscl
* @version 1.0
* @intro: 抽象主题
*/
public interface IHouse {
// 海景房
void seaViewRoom();
// 江景房
void riverViewRoom();
}
- 真实主题(房东,资源的真实拥有者):
package com.pattern.demo01;
/**
* @author lscl
* @version 1.0
* @intro: 房东,房子的实际拥有者(真实对象也叫目标对象)
*/
public class HouseOwner implements IHouse{
@Override
public void seaViewRoom() {
System.out.println("海景房...");
}
@Override
public void riverViewRoom() {
System.out.println("江景房...");
}
}
- 代理主题(中介):
package com.pattern.demo01;
/**
* @author lscl
* @version 1.0
* @intro: 房子的代理商(中介)
*/
public class HouseProxy implements IHouse {
// 拥有房东的引用(资源不在代理对象这里,而是通过真实对象获取到真实资源)
private HouseOwner houseOwner;
public HouseProxy(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
@Override
public void seaViewRoom() {
System.out.println("多收1000元");
// 调用真实资源
houseOwner.seaViewRoom();
System.out.println("豪华级服务...");
}
@Override
public void riverViewRoom() {
System.out.println("多收500元");
houseOwner.riverViewRoom();
System.out.println("贵宾级服务...");
}
}
- 测试代码:
package com.pattern.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_静态代理 {
public static void main(String[] args) {
// 创建一个代理对象
IHouse house=new HouseProxy(new HouseOwner());
house.riverViewRoom();
System.out.println("-------------------");
house.seaViewRoom();
}
}
运行效果:
8.3.3 动态代理的实现
在静态代理中,房东如果新增了新的房源,如果中介要代理心得房源则必须修改源代码,这样一来程序的耦合性高,为了降低耦合,我们提供了动态代理的概念;
动态代理:即代理类在Java运行时生成,并不需要我们自己编写,当我们对目标对象新增或减少方法时,代理对象会自动的增加或减少;Java中实现动态代理有两种方式,分别是JDK提供的动态代理以及Spring框架提供的CGLIB代理;
1)JDK动态代理
JDK动态代理主要是Proxy类来完成的。
- Proxy类:
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 作用:生成一个代理对象 | |
loader | 和目标对象一类的类加载器 |
interfaces | 目标对象所有实现的接口的字节码对象 |
h | 是一个接口,传一个匿名内部类做为实现类,并且重写其中的方法来实现代理的功能 |
返回值 | 返回代理对象 |
- InvocationHandler接口
Object invoke(Object proxy, Method method, Object[] args) 作用:这个接口中的方法会调用多次,每个方法都会调用一次,用来实现代理方法的功能 | |
proxy | 代表生成的代理对象,不建议在方法中直接调用,不然会出现递归调用。 |
method | 目标对象的方法对象 |
args | 调用方法时传递的参数数组 |
返回值 | 返回当前这个方法调用的返回值 |
- 示例代码:
package com.pattern.demo01;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_动态代理 {
public static void main(String[] args) {
// 与目标对象一类的类加载器
ClassLoader appClassLoader = HouseOwner.class.getClassLoader();
// 目标对象实现的所有接口的字节码对象
Class<?>[] targetInterfaces = HouseOwner.class.getInterfaces();
// 调度对象,当代理对象执行方法时,会执行调度对象中的invoke方法
InvocationHandler handler = new InvocationHandler() {
/**
*
* @param proxy: 代理对象
* @param method:
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
System.out.println("执行的方法是: " + methodName);
// 执行目标方法(必须传递目标对象来执行),返回目标方法的返回值
Object returnVal = method.invoke(new HouseOwner(), args);
return returnVal;
}
};
IHouse proxy = (IHouse) Proxy.newProxyInstance(
appClassLoader,
targetInterfaces,
handler
);
proxy.riverViewRoom();
}
}
执行效果:
Tips:JDK的动态代理是基于接口的代理,JDK正是根据传递目标对象实现接口的字节码对象来生成代理类的;
2)CGLIB代理
CGLIB代理是Spring框架提供的API,在spring-core依赖中已经集成,并且GBLIB代理不依赖于接口。CGLIB代理是在运行期间生成一个子类继承目标对象来达到代理的效果的,这样可以保证目标对象的所有方法都能被代理而不依赖与接口;
GBLIB代理的主要API就是Enhancer类。
- Enhancer:
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 作用:生成一个代理对象 | |
loader | 和目标对象一类的类加载器 |
interfaces | 目标对象所有实现的接口的字节码对象 |
h | 是一个接口,传一个匿名内部类做为实现类,并且重写其中的方法来实现代理的功能 |
返回值 | 返回代理对象 |
- 示例代码:
package com.pattern.demo01;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03_CGLIB动态代理 {
public static void main(String[] args) {
Class<HouseOwner> targetClass = HouseOwner.class;
MethodInterceptor methodInterceptor = new MethodInterceptor() {
/**
* 代理对象执行的所有方法都会执行intercept方法
* @param proxy: 代理对象
* @param method: 代理的方法(目标对象的方法)
* @param args: 代理对象在执行方法时传递的参数
* @param methodProxy: 代理对象的方法对象(代理对象的方法)
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String methodName = methodProxy.getSignature().getName();
System.out.println("执行的方法是: " + methodName);
// Object returnVal = method.invoke(new HouseOwner(), args);
// 等价于上面
Object returnVal = methodProxy.invokeSuper(proxy, args);
return returnVal;
}
};
// 使用CGLIB代理的类与目标对象是父子关系
HouseOwner proxy = (HouseOwner) Enhancer.create(targetClass, methodInterceptor);
proxy.riverViewRoom();
}
}
8.3.4 JDK代理和GBLIB代理的区别
- JDK代理:
- 1)针对于接口代理,该代理类实现了真实对象实现的所有方法
- 2)代理出来的代理类和真实对象属于兄弟关系
- CGLIB代理:
- 1)针对于类进行代理,代理类继承了真实对象的所有方法(真实对象不能被final修饰);
- 2)代理出来的代理与真实对象属于父子关系;
8.4 代理设计模式优缺点
- 优点
- 1)动态代理可以保护真实对象,也可以对真实对象进行增强;
- 2)动态代理通过运行时生成代码的方式,取消了对真实对象的扩展限制,遵循开闭原则;
- 缺点:
- 1)代理模式造成系统中的类增多
- 2)增加了系统的复杂度