Java23种设计模式系列——结构型模式day3-1
结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。
它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者采用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足"合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分类
- 代理模式
- 适配器模式
- 装饰者模式
- 桥接模式
- 外观模式
- 组合模式
- 享元模式
代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
代理模式结构
代理(Proxy)模式分为三种角色:
- 抽象主题(subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类︰提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
代理的优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
代理的缺点
增加了系统的复杂度
使用场景
远程(Remote) 代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口, 通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
防火墙(Firewall) 代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
保护(Protect or Access) 代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
静态代理
示例
设计思路
- 使用Sell抽象主题定义需要实现的方法
- 让真实主题类对抽象主题类进行实现
- 代理同样实现抽象主题类,但内部方法则是调用真实主题类完成
- 达到访问代理间接访问真实主题类
Sell(抽象主题)
package construct_pattern.static_proxy;
public interface Sell {
void sellThings();
}
Seller(真实主题类)
package construct_pattern.static_proxy;
public class Seller implements Sell{
@Override
public void sellThings() {
System.out.println("卖二手书");
}
}
PointProxy(代理点)
package construct_pattern.static_proxy;
public class PointProxy implements Sell{
private Seller seller = new Seller();
@Override
public void sellThings() {
seller.sellThings();
System.out.println("售卖点委托销售");
}
}
测试类
package construct_pattern.static_proxy;
public class Buyer {
public static void main(String[] args) {
PointProxy pointProxy = new PointProxy();
pointProxy.sellThings();
}
}
动态代理(JDK代理)
JDK提供动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newEroxyInstance方法)来获取代理对象
Proxy
import java.lang.reflect.Proxy;
Proxy调用Proxy.newProxyInstance()
方法进行实例化,有三个参数
- ClassLoader loader :类加载器,可获取目标对象的类加载器
- Class<?>[] interfaces:代理类实现的接口的字节码对象
- InvocationHandler hander :代理对象的调用处理程序
InvocationHandler中的invoke方法
该方法有三个参数
- Object proxy:代理对象,基本很少用到
- Method method:对接口中的方法进行封装的method方法
- Object[] args:调用方法的实际参数
返回值:调用的方法的返回值
以下代码为示例
//返回代理对象
Sell proxyInstance = (Sell) Proxy.newProxyInstance(
seller.getClass().getClassLoader(),
seller.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("代理点代售(JDK)");
Object invoke = method.invoke(seller, args);
return invoke;
}
);
完整代码如下
package construct_pattern.dynamic_proxy.jdk_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 获取代理对象的工厂类
*/
public class ProxyFactory {
private Seller seller = new Seller();
public Sell getSellObj(){
//返回代理对象
Sell proxyInstance = (Sell) Proxy.newProxyInstance(
seller.getClass().getClassLoader(),
seller.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("代理点代售(JDK)");
Object invoke = method.invoke(seller, args);
return invoke;
}
);
return proxyInstance;
}
}
示例
通过使用JDK的动态代理进行实现
- 使用代理工厂实现JDK代理
- Proxy中采用反射构建
- 自动调用invoke方法
- 最后返回代理对象实例
Sell(抽象主题类)
package construct_pattern.dynamic_proxy.jdk_proxy;
public interface Sell {
void sellThings();
}
Seller(真实主题类)
package construct_pattern.dynamic_proxy.jdk_proxy;
public class Seller implements Sell {
@Override
public void sellThings() {
System.out.println("卖二手书");
}
}
ProxyFactory(代理工厂)
package construct_pattern.dynamic_proxy.jdk_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 获取代理对象的工厂类
*/
public class ProxyFactory {
private Seller seller = new Seller();
public Sell getSellObj(){
//返回代理对象
Sell proxyInstance = (Sell) Proxy.newProxyInstance(
seller.getClass().getClassLoader(),
seller.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("代理点代售(JDK)");
Object invoke = method.invoke(seller, args);
return invoke;
}
);
return proxyInstance;
}
}
测试
package construct_pattern.dynamic_proxy.jdk_proxy;
public class Buyer {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
//使用工厂对象获取代理对象
Sell sellObj = proxyFactory.getSellObj();
sellObj.sellThings();
}
}
动态代理(CGLIB)
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
示例
引入jar包
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
Seller(真实主题类)
package example;
public class Seller {
public void sellThings() {
System.out.println("售卖三手书");
}
}
ProxyFactory
- 实现MethodInterceptor接口
- 编写代理方法
- 创建Enhancer对象
- 设置父类字节码对象
- 设置回调(传入当前对象,需实现MethodInterceptor接口)
- 实现intercept方法使用反射对真实主题中的方法进行调用
- 创建代理对象
package example;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 代理对象工厂,使用cglib
*/
public class ProxyFactory implements MethodInterceptor {
private Seller seller = new Seller();
public Seller getProxyobj(){
//创建Enhancer对象相当于Proxy
Enhancer enhancer = new Enhancer();
//设置父类字节码对象
enhancer.setSuperclass(Seller.class);
//设置回调(传入当前对象,需实现MethodInterceptor接口)
enhancer.setCallback(this);
//创建代理对象
Seller proxyObj = (Seller) enhancer.create();
return proxyObj;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib进行动态代理");
return method.invoke(seller,objects);
}
}
测试
package example;
public class App
{
public static void main( String[] args )
{
ProxyFactory proxyFactory = new ProxyFactory();
Seller proxyobj = proxyFactory.getProxyobj();
proxyobj.sellThings();
}
}
静态代理、动态代理(JDK、CGLIB)对比
jdk代理和CGLIB代理
使用CGLIB实现动态代理, CGLIB底层采用ASM字节码生成框架, 使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLIB不能对声明为final的类或者方法进行代理,因为CGLIB原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7. JDK1 .8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,JDK1. 6和JDK1. z比CGLIB代理效率低一点,但是到JDK1. 8的时候,JDK代理效率高于CGLIB代理。 所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler. invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂
度。而动态代理不会出现该问题