Spring系列设计模式之代理模式
Posted 一宿君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring系列设计模式之代理模式相关的知识,希望对你有一定的参考价值。
4 代理模式
代理模式是java中常用的设计模式,代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤信息、把消息转发给委托类、以及事后处理信息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与其委托类对象的关联(eg:Spring中Bean之间的依赖关系)。 代理类对象本身并不真正实现服务,而是通过调用委托类对象的相关方法来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理类对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种服务。
代理模式又分为静态代理和动态代理(JDK动态代理和CGLIB动态代理)。
4.1 代理模式的构成部分(简图)
Subject(共同接口):客户端使用的共有接口;
RealSubject(真实对象):真实对象的类(要代理的类);
ProxySubject(代理对象):代理类。
4.2 静态代理
静态代理:由程序员创建或根据特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类(真实类)、代理类等确定下来,在程序运行前,代理类的.class文件就已经生成。
为了理解静态代理,我们以一个实例演示:我们知道人类有很多种类(暂时不说语言),中国人、美国人等等,那么人类就可以是一个公共接口,中国人和美国人是其实现类,那么到底是中国人说话还是美国人说话,我们可以通过一个人类的代理类去管理,不管是哪种人类要说话,代理类都可以在其说话前后做一些业务处理,记录日志等操作。
- 首先创建公共接口Human(上述对应途中Subject),其中由说话方法speak():
public interface Human { /** * 说话 * @param name */ void speak(String name); }
- 中国人和美国人(上述对应途中realSubject)共同实现人类Human接口:
//中国人 public class Chinese implements Human { @Override public void speak(String name) { System.out.println("你好:" + name); } } //美国人 public class American implements Human { @Override public void speak(String name) { System.out.println("hello: " + name); } }
- 人类的代理类StaticProxyHuman(上述对应途中ProxySubject)(也要实现共同接口Human):
public class StaticHumanProxy implements Human { private Human human;//代理对象 /** * 类加载器(加载指定实例对象) * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public StaticHumanProxy() throws ClassNotFoundException, IllegalAccessException, InstantiationException { this.human = (Human) this.getClass().getClassLoader().loadClass("com.dao.impl.Chinese").newInstance(); } /** * 装饰器(增前接口) * @param human */ public StaticHumanProxy(Human human) { this.human = human; } /** * 代理方法 * @param name */ public void speak(String name) { System.out.println("speak方法执行前!"); human.speak(name); System.out.println("speak方法执行后!"); } }
- TestStaticProxyHuman类:
public class TestStaticProxyHuman { public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException { /** * 第一种方式:通过无参构造函数,类加载器加载实例对象(Chinese) */ StaticHumanProxy proxy1 = new StaticHumanProxy(); proxy1.speak("二狗子"); /** * 第二种方式:通过带参构造函数传值(传入指定实现类对象American) */ StaticHumanProxy proxy2 = new StaticHumanProxy(new American()); proxy2.speak("特朗普"); } }
- 控制台结果
解释:
- 第一种方式:在无参构造函数中采用类加载器的形式,去加载实例对象,这样我们就不用担心到底什么时候需要真实的实例化对象了,需要的时候,类加载会自动帮我们加载。
- 第二种方式:通过在带参构造函数传值的方式,把指定实例化对昂传过来,可以理解为装饰器。
区别一下:
- 代理模式是提供完全相同的接口,而装饰器是为了增强接口。
4.3 动态代理
上述静态代理的实例,整体比较简单,但是缺点也很明显,那就是静态代理需要为每一个接口实现类对象都创建一个代理类,也就是一个代理类只能为固定的接口以及实现类提供服务,而且是要事先为要代理的实现类创建一个固定代理类,很显然这增加了维护成本和开发成本,且不够灵活,那么为了解决这个问题就延伸出了动态代理,单从名字就可以看出,动态代理类不再局限在为某一个固定的接口及实现类提供服务,是为所有的接口提供公共的服务,在运行时通过反射机制为要代理的接口实现类创建代理类及提供服务。
代理类在程序运行时创建的代理方式被称为动态代理。
相比较静态代理,静态代理类是自己定义好的,在程序运行之前就已经编译完成;而动态代理类不是Java代码中定义的,而是根据我们在Java代码中的“指示(传入的参数)”动态生成的,动态代理的优势在于可以很方便的对代理类的方法进行统一的处理,而不要修改每个动态生成的代理类中的方法。
在写动态代理的时候需要两个很重要的东西(都在java.lang.reflect包下):
- Proxy类,可以理解为是调度器;
- InvocationHandler增强服务接口,可以理解为是代理器。
通过这个类和这个接口可以生成JDK动态代理类和动态代理对象,总的来说动态代理可以理解为是一种对事件的监听。
废话少说,还是直接上代码演示实例操作:
-
创建动态代理类LogHandler,要实现InvocationHandler调度器接口:
public class LogHandler implements InvocationHandler{ private Object sub; public LogHandler(Object sub) { this.sub = sub; } /** * @param proxy 代理类——代理对象,调用方法的代理实例 * @param method 我们所要调用某个对象真实方法的method对象 * @param args 指代理对象传递进来的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置操作!"); Object obj = method.invoke(sub,args);//代理执行目标方法 System.out.println("后置操作!"); return obj; } }
-
TestLoghandler测试类:
public class TestLogHandler { public static void main(String[] args) { Chinese chinese = new Chinese(); InvocationHandler logHandler1 = new LogHandler(chinese); /** * classloader对象:定义了由哪个classloader对象(真实类)对生成的代理类进行加载 * interface对象数组:表示我们将要给我们的代理对象提供一组什么样的接口(真实类对象实现的接口), * InvocationHandler:表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用要代理的方法。 */ Human humanProxy= (Human) Proxy.newProxyInstance(chinese.getClass().getClassLoader(),chinese.getClass().getInterfaces(),logHandler1); humanProxy.speak("二狗子"); } }
-
控制台信息:
-
上述我们说了,动态代理在运行时会通过反射机制为要代理的接口实现类创建代理类及提供服务,所有我们可以再创建一个新的接口和其实现类,同样可以使用这个动态代理类Loghandler,来代理。
-
创建鸟类接口Bird,同样创建一个speak()方法:
public interface Birds { /** * 说话 * @param name * @return */ String speak(String name); }
-
创建鹦鹉实现类Parrot实现Bird接口:
public class Parrot implements Birds { @Override public String speak(String name) { System.out.println("执行方法:嘎嘎嘎 " + name); return "返回值:" + name ; } }
-
TestLogHandler测试类:
public class TestLogHandler { public static void main(String[] args) { Parrot parrot = new Parrot(); InvocationHandler logHandler2 = new LogHandler(parrot); Birds birdProxy= (Birds)Proxy.newProxyInstance(parrot.getClass().getClassLoader(),parrot.getClass().getInterfaces(),logHandler2); birdProxy.speak("鹦鹉"); } }
-
控制台信息:
综上所述,我们以第一个Human接口为总结,创建了一个需要被代理的实现类Chinese对象,将Chinese对象传入到logHandler1中,我们在创建代理对对象humanProxy时,将logHandler1也作为了参数,其实我们所有执行代理对象的方法都会被替换成执行invoke()方法,也就是说最后执行的就是Loghandler中的invoke()方法。
动态代理的优势在于可以很方便的对代理类的方法进行统一的处理,而不用修改每个动态生成的代理类中的方法。 是因为所有被代理执行的方法,都是通过InvocationHandler中的invoke方法调用的,所以我们 只要在invoke方法中做统一管理,就可以对所有被代理的方法进行相同的操作了。
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们实际上并没有看到真实的代理类,而且动态代理中被代理的对象和代理对象是通过InvocationHandler来完成的代理过程,其中具体是怎么操作的?为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行?想弄清楚这些问题,我们就要对java动态代理的源码进行简要的分析。
4.4 动态代理原理分析
我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤:
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces)
{
// optimization for single interface
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// interfaces cloned
final Class<?>[] intfsArray = interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
}
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (caller != null) {
checkNewProxyPermission(caller, cons.getDeclaringClass());
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
}
}
告诉我是不是看不懂?是不是?没事不要紧,真看不懂就使劲看,咱们重点看这几处:
//调用代理类构造方法
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
//接口数组对象
final Class<?>[] intfsArray = interfaces.clone();
//构造器对象
final Constructor<?> cons;
try {
cons = proxyClass.getConstructor(constructorParams);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
//将生成的代理类缓存到java虚拟机中
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
//将生成的代理类返回
return cons.newInstance(new Object[]{h});
看这句return cons.newInstance(new Object[]{h}); 这里说明已经产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,这里及不具体分析如何产生的这份类文件了,通过
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
我们可以知道生成的代理类文件是缓存在java虚拟机中的,这个类文件放在内存中,我们在创建代理类对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。
我们可以把InvicationHandler看做是一个中介类,中介类持有一个被代理对象,在invoke方法中调用被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己的方法时,通过自身持有的中介类InvocationHandler对象来调用中介类对象的方法,从而达到代理执行被代理对象的方法。也就是说代理对象通过中介类实现了具体的代理功能。
生成的java动态代理类只能对接口进行处理,Java的继承机制决定了这些动态代理类无法实现对class的动态代理。上述动态代理的实例,其实就是Spring中AOP的一个简单实现了,在目标对象执行之前和之后进行一定的事务处理。还有Spring中AOP面向切面的编程也是用了Proxy和InvocationHandler这两个东西。
4.5 JDK动态代理和CGLIB动态代理代码实例演示
- 定义用户管理接口:
public interface UserManager { /** * 添加用户 * @param name * @param pwd */ void addUser(String name,String pwd); /** * 删除用户 * @param name */ void delUser(String name); }
- 定义用户管理接口实现类:
public class UserManagerImpl implements UserManager { @Override public void addUser(String name, String pwd) { System.out.println("调用了用户新增的方法!"); System.out.println(""传入参数:name = " + name +", pwd = " + pwd); } @Override public void delUser(String name) { System.out.println("调用了删除的方法!"); System.out.println("传入参数:name= " + name); } }
- 采用JDK动态代理实现InvocationHandler接口
public class JdkProxy implements InvocationHandler { private Object targetObject; //需要代理的目标对象 public JdkProxy(Object targetObject) { this.targetObject = targetObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("JDK动态代理,监听开始!"); // 调用invoke方法,result存储该方法的返回值 Object obj = method.invoke(targetObject,args); System.out.println("JDK动态代理,监听结束!"); return obj; } }
- TestJdkProxy测试类:
public class TestJdkProxy { public static void main(String[] args) { UserManager userManager = new UserManagerImpl(); InvocationHandler userHandler = new JdkProxy(userManager); UserManager userProxy = (UserManager) Proxy.newProxyInstance(userManager.getClass().getClassLoader(),userManager.getClass().getInterfaces(),userHandler); userProxy.addUser("zs","123456"); } }
- 采用CGLIB动态代理实现:需要导入asm版本包,实现MethodInterceptor接口
- Cglib动态代理:(需要导入两个jar包,asm-5.0.3.jar,cglib-3.1.jar 版本可自行选择)
- CglibProxy代理类:
以上是关于Spring系列设计模式之代理模式的主要内容,如果未能解决你的问题,请参考以下文章
Java之Spring AOP入门到精通IDEA版(一篇文章精通系列)