一、代理模式的应用场景

  生活中的中介,黄牛,等一系列帮助甲方做事的行为,都是代理模式的体现。代理模式(Proxy Pattern)是指为题对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,代理模式属于结构型设计模式。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些处理代码,在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑。代理模式属于结构型模式,分为静态代理和动态代理。

二、静态代理

  来看一个实际业务场景:在分布式业务场景中,通常会对数据库进行分库分表,可能需要配置多个数据源,通常会设置数据源路由来动态切换数据源。下面是实现过程:

/**
* 订单类
*/
@Data
public class Order { private Object orderInfo; private Long createTime; private String id;
}
/**
* 持久层
*/
@Compoment
public class OrderDao {
public int insert(Order order) {
System.out.println("OrderDao 创建 Order 成功!");
return 1;
}
}
public interface IOrderService {
int createOrder(Order order);
}
@Service
public class OrderService implements IOrderService { private OrderDao orderDao; @Override
public int createOrder(Order order) {
return 0;
}
}

  接下来使用静态代理,主要完成功能:根据订单创建时间自动按年进行分库。根据开闭原则,我们修改原来写好的代码逻辑,通过代理对象完成。先创建数据源路由对象,使用ThredLocal的单利实现DynamicDataSoureEntry类:

/**
* 动态切换数据源
*/
public class DynamicDataSoureEntry { //默认数据源
public final static String DEFAULT_SOURCE = null; private final static ThreadLocal<String> local = new ThreadLocal<>(); //清空数据源
public static void clear() {
local.remove();
} //获取当前正在使用的数据源名字
public static String get() {
return local.get();
} //还原当前切换的数据源
public static void restore() {
local.set(DEFAULT_SOURCE);
} //设置一直名字的数据源
public static void set(String source) {
local.set(source);
} //根据年份动态设置数据源
public static void set(int year) {
local.set("DB_" + year);
}
}

  创建切换数据源的代理类OrderServiceSaticProxy:

public class OrderServiceStaticProxy implements IOrderService {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    private IOrderService orderService;

    @Override
public int createOrder(Order order) {
before();
Long time = order.getCreateTime();
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到[DB_]" + dbRouter + "]数据源处理数据");
DynamicDataSoureEntry.set(dbRouter);
orderService.createOrder(order);
after();
return 0;
} private void before() {
System.out.println("Proxy before method.");
} private void after() {
System.out.println("Proxy after method.");
}
}

三、动态代理

  动态代理和静态代理的基本思路是一致的,只不过动态代理功能更强大,随业务的扩展性更强。

1.JDK实现方式(必须实现接口)

  上面的例子改为动态代理:

public class OrderServiceDynamicProxy implements InvocationHandler {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    private Object target;

    public Object getInstance(Object target) {
this.target = target;
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]);
return null;
} private void before(Object target) {
try {
System.out.println("Proxy before method.");
Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到[DB_]" + dbRouter + "]数据源处理数据");
DynamicDataSoureEntry.set(dbRouter);
after();
} catch (Exception e) {
e.printStackTrace();
}
} private void after() {
System.out.println("Proxy after method.");
}
}

  使用动态代理之后,不仅能实现Order的数据源动态路由,还可以实现其他任何类的数据源路由。

  JDK代理是如何实现的呢?JDK动态代理采用字节重组,重新生成对象来代替原始对象,以达到动态代理的目的。动态代理生成对象的步骤如下:

  1. 获取被代理对象的引用,并且获取它的所有接口,通过反射获得。
  2. JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。
  3. 动态生成Java代码,新加的业务逻辑方法有一定的逻辑代码调用(在代码中体现)。
  4. 编译新生成的Java代码 .class文件。
  5. 重新加载到JVM运行。

2.CGLib代理调用API及原理分析

public class CGLibTest implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) throws Exception {
Enhancer enhancer = new Enhancer();
//要把哪个设置为即将生成的新类的父类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
} @Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object object = methodProxy.invokeSuper(o, objects);
after();
return object;
} private void before() {
System.out.println("before");
} private void after() {
System.out.println("after");
}
}

  CGLib代理的目标对象不需要事先任何接口,它是通过动态集成目标对象实现动态代理的。CGLib代理执行代理方法的效率之所以比JDK高,是因为CGLib采用了FastClass机制:为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法分配一个index(int类型);这个index当作一个入参,FastClass 就可以直接定位要调用的方法并直接进行调用,省去了反射调用,所以调用效率比JDK代理通过反射调用高。FastClass并不是跟代理类一起生成的,而是在第一次执行MethodProxy的invoke()或invokeSuper()方法时产生并放在缓存中的。

3.CGLib和JDK动态代理对比

  • JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象。
  • JDK动态代理和CGLib代理在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架(字节码操控框架)写Class字节码,CGLib代理实现更复杂,生成代理类比JDK动态代理效率低。
  • JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高。

4.代理模式与Spring

1.代理模式在Spring源码中的应用

  先看ProxyFactoryBean核心方法getObject()

    public Object getObject() throws BeansException {
this.initializeAdvisorChain();
if (this.isSingleton()) {
return this.getSingletonInstance();
} else {
if (this.targetName == null) {
this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
} return this.newPrototypeInstance();
}
}

  在getObject()方法中,主要调用getSingletonInstance()和newPrototypeInstance()。在Spring的配置中如果不做任何配置,那么Spring代理生成的Bean都是单例对象。如果修改scope,则每次创建新原型对象。newPrototypeInstance()里面的逻辑比较复杂,后续再做研究。Spring利用动态代理实现AOP时有两个非常重要的类:JDKDynamicAopProxy类和CGLibAopProxy类。

2.Spring中的代理选择原则

  • 当Bean有实现接口时,Spring就会用JDK动态代理
  • 当Bean没有实现接口时,Spring会选择CGLib代理
  • Spring可以通过配置强制使用CGLib代理,只需要在配置中加入<aop:aspectj-autoproxy roxy-target-clas="true">

四、静态代理和动态代理的本质区别

  1. 静态代理只能通过手动完成代理操作,如果被代理类增加新方法,代理类需要同步增加,违背开闭原则。
  2. 动态代理采用在运行时动态生成代码的方式,取消了被代理类的扩展限制,遵循开闭原则。
  3. 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无须修改代理类的代码。

五、代理模式的优缺点

优点:

  1. 代理模式将代理对象与真是被调用目标对象分离。
  2. 在一定程度上降低了系统的耦合性。扩展性好。
  3. 可以起到保护目标对象的作用。
  4. 可以增强目标对象的功能。

缺点:

  1. 代理模式会造成系统设计中类的数量增加。
  2. 在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢。
  3. 增加了系统的复杂度。

 

Spring中常见的设计模式——代理模式的更多相关文章

  1. Spring中常见的设计模式——委派模式

    一.委派模式的定义及应用场景 委派模式(Delegate Pattern)的基本作用是负责任务的调用和分配,跟代理模式很像,可以看做特殊情况下的静态的全权代理,但是代理模式注重过程,而委派模式注重结果 ...

  2. Spring中常见的设计模式——策略模式

    策略模式(Strategy Pattern) 一.策略模式的应用场景 策略模式的应用场景如下: 系统中有很多类,而他们的区别仅仅在于行为不同. 一个系统需要动态的在集中算法中选择一种 二.用策略模式实 ...

  3. Spring中常见的设计模式——工厂模式

    一.简单工厂模式 简单工厂模式(Simple Factory Pattern)由一个工厂对象决定创建哪一种产品类的实例,简单工厂模式适用于工厂类负责创建对象较少的情况,且客户端只需要传入工厂类的参数, ...

  4. Spring中常见的设计模式——原型模式

    1.原型模式应用场景 当遇到大量耗费劳动力的 get,set赋值场景时,如下: public class SetGetParam { public void setParam(UserDto user ...

  5. Spring中常见的设计模式——模板模式

    一.模板模式的应用场景 模板模式又叫模板方法模式(Template Method Pattern),指定义一个算法的骨架,并允许自雷为一个或者多个步骤提供实现.模板模式使得子类可以在不改变算法结果的情 ...

  6. Spring中常见的设计模式——适配器模式

    一.适配器模式的应用场景 适配器模式(Adapter Pattern)是指将一个类的接口转换成用户期待的另一个接口,使原本接口不兼容的类可以一起工作,属于构造设计模式. 适配器适用于以下几种业务场景: ...

  7. 设计模式:JDK和Spring中常见的设计模式

    设计模式 总结 类 工厂模式 封装创建过程,只对结果负责 BeanFactory.Calender 单例模式 全局唯一 ApplicationContext.Calender 原型模式 多重影分身之术 ...

  8. Spring中常见的设计模式——单例模式

    一.单例模式的应用场景 单例模式(singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点.J2EE中的ServletContext,ServletCon ...

  9. spring 中常用的设计模式

    一. Spring 中常见的设计模式 工厂模式 : BeanFactory 装饰器模式: BeanWrapper 代理模式: AopProxy 单例模式: ApplicationContext 委派模 ...

随机推荐

  1. saltStack 配置管理(也就是替换文件)

    目录  /srv/salt/base下面新建一个文件dns.sls /opt/resolv.conf_bak:     #这个是文件替换的位置,也就说替换到远程文件的/opt/resolv.conf_ ...

  2. 2018-8-10-win10-uwp-如何打包Nuget给其他人

    title author date CreateTime categories win10 uwp 如何打包Nuget给其他人 lindexi 2018-08-10 19:16:50 +0800 20 ...

  3. Python基础之(三)----PyGame安装步骤

    http://blog.csdn.net/qq_33166080/article/details/68928563 如果你已经有一定的编程经验,那么学习一门新语言最快的方式就是拿着一个比较中型的项目, ...

  4. adblock自定义规则

    click.admaster.cn/* cm.baidu.com/* cm.pos.baidu.com/* cpro.baidu.com/* cpro.baidustatic.com/* dup.ba ...

  5. C++ 第四次作业 继承

    继承 继承时从新的类从已有类那里得到新的特征.继承实现了代码的重用,极大地减少了代码量,同时通过新增成员,加入了自身的独有特性,达到了程序的扩充. 派生类继承了基类的全部数据类和除了构造函数.析构函数 ...

  6. 在 Windows Azure 中运行SuperSocket

    关键字: Windows Azure, WorkRole, InputEndPoint, 云计算, 微软云 什么是 Windows Azure? Windows Azure 是微软的云计算平台!微软的 ...

  7. Roslyn 静态分析

    本文告诉大家如何使用 Roslyn 分析代码 首先创建一个项目,项目使用.net Framework 4.6.2 ,控制台项目.然后需要安装一些需要的库 Nuget 安装 打开 Nuget 安装下面两 ...

  8. Codeforces Round #181 (Div. 2)

    A. Array 模拟. B. Coach 模拟. C. Beautiful Numbers good number的位和最大不超过\(10^7\),那么只要枚举a或b的个数,然后最多循环7次判断位和 ...

  9. tomcat最大线程数的设置

    Tomcat的server.xml中连接器设置如下 <Connector port="8080" maxThreads="150" minSpareThr ...

  10. 2019-5-21-win10-uwp-商业游戏-1.1.5

    title author date CreateTime categories win10 uwp 商业游戏 1.1.5 lindexi 2019-05-21 11:38:20 +0800 2018- ...