浅谈java代理模式
讲解java代理模式
何谓代理模式
代理模式
,即Proxy Pattern,23种java常用设计模式之一。代理模式提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
通俗来说,代理模式就相当于我们现实生活中的中介。举个例子,比如我们想要购买某件东西,比如衣服,电脑等,我们一般都不会选择直接去练习厂家购买,而是从厂家的代理商那里去买。比如我们在淘宝上买东西,淘宝肯定是不生产衣服的,他只是在我们和厂商之间搭桥,这里就可以认为淘宝起的就是代理的作用。
那么在我们软件开发中,如何应用代理模式呢?他又具体分为哪些呢?
代理模式
是面向对象编程中比较常见的一种设计模式,这是常见代理模式的UML示意图:
在代理模式中:
- 用户只关心具体的功能,而并不关心到底是谁提供的,即被代理的接口
Subject
- 上图得知,真正实现者是
RealSubject
,但是他并不会与用户直接接触,而是交给Proxy
代理 - 代理就是上图中的
Proxy
,它也实现了Subject 接口
,所以它能够直接与用户接触 - 用户调用
Proxy
的时候,其实内部调用了RealSubject
,所以Proxy
相当于中介,它可以增强RealSubject
操作
代理模式的作用:
- 功能增强:原有功能添加额外功能
- 控制访问:代理类不让你访问目标
在java中,实现代理模式主要有两种方式:静态代理,动态代理,下面逐一进行讲解。
这里我先声明几个例类,方便下面演示:
-
创建一个接口类
UserDao
:package com.soberw.example.dao; /** * @author soberw * @Classname UserDao * @Description * @Date 2022-02-13 13:50 */ public interface UserDao { void show(); }
-
创建一个实现类
UserDaoImpl
,即被代理的目标对象package com.soberw.example.dao; /** * @author soberw * @Classname UserDaoImpl * @Description * @Date 2022-02-13 13:51 */ public class UserDaoImpl implements UserDao{ //实现了某一功能 @Override public void show() { System.out.println(" show something ...... "); } }
静态代理
之所以是静态代理,是因为他是在事先预定好的,即程序在运行之前,我们就确定了委托对象,通过代理类,我们可以实现在不改变原来功能的基础上,对原有接口功能的功能进行再拓展。
以上面例子说明:
-
此时如果调用show()方法自然会正常执行方法流程
创建测试方法执行:
@Test public void testProxy1(){ UserDao userDaoImpl = new UserDaoImpl(); userDaoImpl.show(); }
-
但是现在想在方法中添加新的实现功能,我们这时候当然可以直接去修改实现类UserDaoImpl的源码,但是会导致:
-
- 源码可能改动比较多
-
- 重复代码比较多
-
- 代码维护性低
-
-
因此我们可以使用代理完成对功能的拓展,那么如何通过静态代理实现呢?
-
使用
静态代理
,需要我们代理对象和目标对象都实现同样的接口 -
因此,这里我声明一个代理类
UserProxyStatic
package com.soberw.example.proxy; import com.soberw.example.dao.UserDao; /** * @author soberw * @Classname UserProxy * @Description * @Date 2022-02-13 13:54 */ public class UserProxyStatic implements UserDao { private UserDao userDao; public UserProxyStatic(UserDao userDao){ this.userDao = userDao; } @Override public void show() { //加入额外的拓展功能 System.out.println("something in show() before..."); userDao.show(); System.out.println("something in show() after..."); } }
-
测试执行:
@Test public void testProxy2(){ UserDao userDaoImpl = new UserDaoImpl(); UserDao userProxy = new UserProxyStatic(userDaoImpl); userProxy.show(); }
这就实现了对原有功能的拓展,并且不改变源码。
总结一下:使用静态代理可以在不修改目标对象的前提下拓展目标对象的功能
但是其确定也非常明显:
- 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
- 不易维护。一旦原接口增加方法,目标对象与代理对象都要进行修改。
动态代理
于是为了解决静态代理的一些弊端,就有了动态代理
,将程序的执行抉择放到了运行时,动态地创建代理对象,从而实现对目标对象的代理功能。
静态代理与动态代理的主要区别在于:
- 静态代理在编译时已经实现,编译后会生成对应的实际的class文件
- 动态代理是在运行时产生的,是在运行时动态的生成类字节码,并加载到了JVM中
- 动态代理实际上用的就是反射的机制
而实现动态代理的方式又可分为两种:
- JDK动态代理
- CGLIB动态代理
JDK动态代理
JDK动态代理,实际上就是使用java官方给我们提供的一些API去实现的,涉及到的类都存在于java.lang.reflect包下
Proxy 核心类,通过调用此类的newInstance()
静态方法生成代理类
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参数说明:
ClassLoader loader
:类加载器,通过反射获取对象并向内存中加载Class<?>[] interfaces
:接口集合,增强方法所在的类,即目标对象所实现的接口InvocationHandler h
:具体要完成哪些功能,即代理对象返回值说明:
返回指定的接口的一个代理类实例对象
InvocationJandler 调用处理器,内部就一个方法invoke()
,表示代理对象要执行的具体的方法,相当于中介类,调用目标方法,可在invoke()内部写入我们的增强功能:
public Object invoke(Object proxy, Method method, Object[] args)
参数说明:
Object proxy
:代理的对象Method method
:代理对象调用的方法Object[] args
:方法参数返回代理的对象
Method 协助代理类完成方法的调用操作,主要用的是内部的invoke()
方法,注意,该invoke方法时具体的方法,而上面的invoke()是一个接口方法:
public Object invoke(Object obj, Object... args)
参数说明:
Object obj
:调用的对象Object... args
:方法参数返回方法的返回值
实际上:
如果我们的目标对象中有多个方法都需要增强业务,我们可以通过
method.getName()
方法获取方法名,然后根据不同的方法完成不同的操作逻辑。
具体实现如下:
-
创建一个类作为代理类
UserDaoProxy
:package com.soberw.example.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author soberw * @Classname UserDaoProxy * @Description * @Date 2022-02-13 17:13 */ public class UserDaoProxy { private UserDaoProxy(){ } //传入目标对象,返回代理后的对象 public static Object getProxyInstance(Object obj) { //调用代理类,传入参数,其中包括一个内部匿名类 return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在方法之前加入额外功能 System.out.println("something in show() before..."); //调用原本的方法,并接收返回值 Object result = method.invoke(obj, args); //在方法之后加入额外功能 System.out.println("something in show() after..."); //返回新方法的返回值 return result; } }); } }
-
测试起来也很简单:
@Test public void testProxy3(){ //创建目标对象实例并传入即得到代理对象 UserDao userDao = new UserDaoImpl(); UserDao dao = (UserDao) UserDaoProxy.getProxyInstance(userDao); dao.show(); }
-
但是实际上我们一般也不会去创建内部匿名类,下面提供另外一种写法:
-
创建一个
InvocationJandler
的实现类UserProxyDynamic
:package com.soberw.example.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author soberw * @Classname UserProxyDynamic * @Description * @Date 2022-02-13 15:56 */ public class UserProxyDynamic implements InvocationHandler { //创建的是谁的代理对象,就把谁传递进来 Object obj; //有参数构造传递 public UserProxyDynamic(Object obj){ this.obj = obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在方法之前加入额外功能 System.out.println("something in show() before..."); //调用原本的方法,并接收返回值 Object result = method.invoke(obj, args); //在方法之后加入额外功能 System.out.println("something in show() after..."); //返回新方法的返回值 return result; } }
-
进行测试:
@Test public void testProxy4(){ //创建目标对象实例并传入即得到代理对象 UserDao userDao = new UserDaoImpl(); UserDao dao = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new UserProxyDynamic(userDao)); dao.show(); }
动态代理对象不需要实现接口,但是要求目标对象必须实现接口InvocationJandler,否则不能使用动态代理。
CGLIB动态代理
CGLIB
(Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
前面说到限制动态代理的对象必须实现一个或多个接口,而使用CGLIB
就不用,真正的达到了代理类无侵入
。
使用CGLIB需要引入CGLIB的jar包,或者如果你是maven项目,引入依赖:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
还是以实现上面例子为目标:
-
创建一个代理对象类
UserDaoCGLIB
,实现CGLIB提供的类MethodInterceptor
:package com.soberw.example.proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @author soberw * @Classname UserDaoCGLIB * @Description * @Date 2022-02-13 18:42 */ public class UserDaoCGLIB implements MethodInterceptor { private Object obj; public UserDaoCGLIB(Object obj) { this.obj = obj; } public Object getProxyInstance() { //工具类 Enhancer en = new Enhancer(); //设置父类 en.setSuperclass(obj.getClass()); //设置回调函数 en.setCallback(this); //创建子类对象代理 return en.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //在方法之前加入额外功能 System.out.println("something in show() before..."); //调用原本的方法,并接收返回值 Object result = method.invoke(obj, objects); //在方法之后加入额外功能 System.out.println("something in show() after..."); //返回新方法的返回值 return result; } }
-
测试:
@Test public void testProxy5(){ UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao) new UserDaoCGLIB(userDao).getProxyInstance(); dao.show(); }
CGLIB代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但CGLIB会继承目标对象,需要重写方法,所以目标对象不能为final类。
但是毋庸置疑的是,CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。