gof23——代理模式详解
一、代理模式和结构型设计模式
代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制
对这个对象的访问。代理对象在客户端和目标之前起到了中介作用,代理模式属于结构型设计模式。使用代理模式主要有两个目的:一是保护目标对象,而是增强目标对象。
要弄清楚代理模式,首先我们需要了解什么是结构型设计模式。结构型设计模式存在的目的主要是:在解决了对象的创建问题之后,对象的组成和对象之间的依赖关系就成了开发人员关注的焦点。因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等等。因此,结构型设计模式最主要涉及和关心的是如何组合类和对象来获得更大的结构。结构型设计模式采用继承机制来组合接口火实现(也称为类结构型模式),或者通过组合一些对象从而实现一些新的功能(也称为对象结构型模式)。
二、静态代理
1.静态代理使用的前提:
- 1.代理类和被代理类继承同一个接口
- 2.代理类中持有被代理类对象
- 3.由代理类来控制被代理类的行为(即类的方法)
2.代码示例
以买票的行为为例,现在我们买票可以直接在12306上进行购票,也可以通过第三方平台如智行等进行购票
购票接口:
public interface BuyTicket {
/**
* 购票
*/
void buyTicket();
}
被代理对象:
public class User implements BuyTicket{
/**
* 购票
*/
@Override
public void buyTicket() {
System.out.println("用户:请求购票");
}
public void offerInfo(){
System.out.println("用户:提供购票需要的个人信息");
}
}
代理对象:
public class BuyTicketProxy implements BuyTicket{
private User user;
public BuyTicketProxy(User u){
user=u;
}
/**
* 购票
*/
@Override
public void buyTicket() {
user.buyTicket();
System.out.println("代理平台:请求获取用户信息");
user.offerInfo();
System.out.println("代理平台:购票完成!!!");
}
}
客户端:
public class Client {
public static void main(String[] args) {
BuyTicketProxy buyTicketProxy=new BuyTicketProxy(new User());
buyTicketProxy.buyTicket();
}
}
从以上代码可以看出,代理平台在进行购票时,实际上仍然是调用用户的功能逻辑,用户自身并不需要关心自己的方法何时使用,交由代理平台控制。
三、静态代理在业务上的应用
在分布式业务中 ,通常会对数据库进行分库分表操作,分库分表之后使用Java操作时就可能需要配置多个数据源,我们通过设置数据源路由来动态切换数据源。
创建订单类:
@Getter
@Setter
@ToString
public class Order {
private String id;
private Object orderInfo;
private Long createTime;
}
创建OrderDao持久层操作
public class OrderDao {
public int insert(Order order) {
System.out.println("OrderDao 创建Order成功");
return 1;
}
}
创建IOrderService接口
public interface IOrderSerivce {
/**
* 创建Order
* @param order order
* @return int
*/
public int createOrder(Order order);
}
创建OrderService实现类
public class OrderService implements IOrderSerivce{
private OrderDao orderDao;
public OrderService(){
orderDao=new OrderDao();
}
@Override
public int createOrder(Order order){
System.out.println("OrderService调用OrderDao创建订单");
return orderDao.insert(order);
}
}
创建数据源路由对象,是与哦给你ThreadLocal的单例对象实现DynamicDataSourceEntry类
public class DynamicDataSourceEntry {
/**
* 默认数据源
*/
public final static String DEFAULT_SOURCE=null;
private final static ThreadLocal<String> LOCAL=new ThreadLocal<>();
private DynamicDataSourceEntry(){}
/**
* 清空数据源
*/
public static void clear(){
LOCAL.remove();
}
/**
* 获取数据源
* @return 数据源
*/
public static String get(){
return LOCAL.get();
}
/**
* 还原当前切换的数据源
*/
public static void restore(){
LOCAL.set(DEFAULT_SOURCE);
}
/**
* 设置数据源
* @param dataSource 数据源
*/
public static void set(String dataSource){
LOCAL.set(dataSource);
}
/**
* 根据年份动态设置数据源
* @param year
*/
public static void set(int year){
LOCAL.set("DB_"+year);
}
}
创建OrderService的静态代理类,主要完成的功能时根据订单创建时间自动按年份选择数据库。
public class OrderServiceStaticProxy implements IOrderSerivce{
private IOrderSerivce orderService;
private SimpleDateFormat yearFormat=new SimpleDateFormat("yyyy");
public OrderServiceStaticProxy(IOrderSerivce orderService){
this.orderService=orderService;
}
/**
* 创建Order
*
* @param order order
* @return int
*/
@Override
public int createOrder(Order order) {
System.out.println("Proxy before method");
Long time=order.getCreateTime();
int dbRouter= Integer.parseInt(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到DB_"+dbRouter+"数据源处理数据");
DynamicDataSourceEntry.set(dbRouter);
int res=orderService.createOrder(order);
System.out.println("Proxy after method");
return res;
}
}
测试类
public class Client {
public static void main(String[] args) throws ParseException {
Order order=new Order();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
Date date=sdf.parse("2020/05/03");
order.setCreateTime(date.getTime());
IOrderSerivce orderService=new OrderServiceStaticProxy(new OrderService());
orderService.createOrder(order);
}
}
四、JDK的动态代理
上述的静态代理需要代理对象和目标对象实现相同的接口,这意味着随着代理对象和目标对象的不断添加,一旦接口增加了方法,目标对象和代理对象都需维护,代码会变得相当繁琐和冗余。
为此我们需要一种适应性更强的代理模式,这里JDK为我们提供了一种基于接口的动态代理。使用JDK的动态代理需要我门使用Proxy类以及InvocationHandler这个接口并实现invoke方法,调用Proxy类的静态方法newProxyInstance()。我们先查看下该方法的源码需要的参数
ClassLoader loader
指定目标对象使用的类加载器,可以使用Object.getClass().getClassLoader()
获取
Class<?>[ ] interfaces
目标对象实现的接口的类型,可用通过Object.getClass().getInterfaces()
获取
InvocationHandler h
事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行的目标对象的方法作为参数传入
现在我们再回顾下刚才的业务,将静态的数据源路由改成动态路由的业务。
创建动态代理类
public class OrderServiceDynamicProxy implements InvocationHandler {
private SimpleDateFormat yearFormat=new SimpleDateFormat("yyyy");
private Object target;
public OrderServiceDynamicProxy(Object target){
this.target=target;
}
public Object getInstance(){
Class<?> clazz=target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(args[0]);
Object object=method.invoke(target,args);
System.out.println("Proxy after method");
return object;
}
private void before(Object obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
System.out.println("Proxy before method");
Long time= (Long) obj.getClass().getDeclaredMethod("getCreateTime").invoke(obj);
int dbRouter= Integer.parseInt(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到DB_"+dbRouter+"数据源处理数据");
DynamicDataSourceEntry.set(dbRouter);
}
}
测试类
public class Client {
public static void main(String[] args) throws ParseException {
Order order=new Order();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
Date date=sdf.parse("2020/05/03");
order.setCreateTime(date.getTime());
IOrderSerivce orderService= (IOrderSerivce) new OrderServiceDynamicProxy(new OrderService()).getInstance();
orderService.createOrder(order);
}
}
五、动态代理源码探索
六、CGLib动态代理
基于子类的动态代理技术,要求被代理类不可被final修饰,使用的是CGLib库提供的Enhance类,调用其create方法创建代理对象。create方法的传入参数为:
Class type
指定被代理对象的字节码文件
callback
回调接口,这里一般使用MethodInterceptor的实现类
继续对之前提到的业务类进行改造
public class OrderServiceCGLibProxy implements MethodInterceptor {
private SimpleDateFormat yearFormat=new SimpleDateFormat("yyyy");
private Object target;
public OrderServiceCGLibProxy(Object target){
this.target=target;
}
public Object getInstance(){
return Enhancer.create(target.getClass(),this);
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before(objects[0]);
Object object=methodProxy.invokeSuper(o,objects);
System.out.println("Proxy after method");
return object;
}
private void before(Object obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
System.out.println("Proxy before method");
Long time= (Long) obj.getClass().getDeclaredMethod("getCreateTime").invoke(obj);
int dbRouter= Integer.parseInt(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到DB_"+dbRouter+"数据源处理数据");
DynamicDataSourceEntry.set(dbRouter);
}
}
七、CGLib的FastClass机制
- cglib代理对象实现后会产生三个.class文件, 这三个文件分别是被代理类的class文件、被代代理类的FastClass文件和代理类的FastClass文件。
- 代理类会获得所有在父类继承来的方法, 并且会有MethodProxy与之对应
- 调用过程:以
createOrder()
方法为例。代理对象调用this.createOrder()方法->调用拦截器->methodProxy.invokeSuper->CGLIB$createOrder()$0->被代理对象createOrder()
方法 - cglib动态代理执行代理方法效率之所以比JDK的高是因为cglib采用了FastClass机制, 原理简单来说就是:为代理类和被代理类各生成一个Class, 这个Class会为代理类或被代理类的方法分配一个index(int类型) 。这个index当做一个入参, Fast Class就可以直接定位要调用的方法直接进行调用, 这样省去了反射调用, 所以调用效率比JDK动态代理通过反射调用高
- FastClass并不是跟代理类一块生成的,而是在第一次执行MethodProxy invoke/invoke Super方法时生成的并放在了缓存中。Method Proxy invoke/invoke Super都调用了
init()
方法,init()
方法中获取index。
八、JDK动态代理和CGib动态代理对比
1.JDK动态代理实现类被代理类的接口,CGLib代理继承了被代理对象。
2.JDK动态代理和CGLib代理都在运行期生成了字节码文件,JDK动态代理直接生成Class字节码文件,而CGLib代理使用ASM框架生成Class字节码文件,CGLib代理实现更加复杂,生成代理类的效率比通过JDK动态代理生成的效率低,即JDK动态代理字节码文件的生成效率高。
3.JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,所以CGLib代理的执行效率高。
九、静态代理和动态代理的区别
1.静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,那么代理类也需要同步增加方法,这一点上违背了开闭原则
2.动态代理采用在运行时动态生成代码的方式,取消了对被代理类扩展的限制,遵循开闭原则
3.动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
十、代理类的优缺点
优点:
- 代理模式能够将代理对象与真实被调用对象分离
- 在一定程度上降低了系统的耦合性,扩展性好
- 可以起到保护目标对象的作用
- 可以增强目标对象的功能
缺点:
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加了一个代理对象,会导致请求的速度变慢
- 增加了系统的复杂度