说起代理模式,相信很多人早已经很理解,但对于我这个菜鸟,理解的可能还是不到位,写一次博客加深印象。
什么是代理模式呢?代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类和委托类之间通常会存在关联关系,
一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不是真正实现服务,而是通过调用委托类对象的相关
的方法来提供特定的服务。
按照代理的创建时期,一般分为两类:
静态代理:由程序员或特定工具自动生成的源代码,再对其编译,在程序运行之前,代理的类编译生成的.class文件就已经存在了
动态代理:在程序运行时,通过反射机制动态创建而成。
重点:
动态代理主要实现使用的是聚合,这种方法更有利于继承
作用:
(1)方法前后加日志,记录时间(方法运行的时间)
(2)方法前后加事务,事务的提交或者回滚(AOP)
(3)方法前后加判断调用者是否拥有权限,用于权限的控制
1、静态代理:
由程序员创建或工具生成代理类的源码,再编译代理类。所谓的静态代理,就是在程序运行前就已经存在的代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
代码如下:
接口Moveable.java
package designMode.proxy;
/*** * @Description * @author CCQ* @date 2017年6月22日 上午11:19:30**/
public interface Moveable {void move();}
Tank.java
package designMode.proxy;import java.util.Random;
/*** * @Description * @author CCQ* @date 2017年6月22日 上午11:19:52**/
public class Tank implements Moveable{@Overridepublic void move() {System.out.println("Tank Moving...");try {Thread.sleep(new Random().nextInt(10000));} catch (InterruptedException e) {e.printStackTrace();}}}
日志代理类TankLogProxy.java
package designMode.proxy;public class TankLogProxy implements Moveable {public TankLogProxy(Moveable t) {super();this.t = t;}Moveable t;@Overridepublic void move() {System.out.println("log Start ...");t.move();System.out.println("log end ...");}}
时间代理类TankTimeProxy.java
package designMode.proxy;public class TankTimeProxy implements Moveable {public TankTimeProxy(Moveable t) {super();this.t = t;}Moveable t;@Overridepublic void move() {long start = System.currentTimeMillis();t.move();long end = System.currentTimeMillis();System.out.println("运行时间:"+(end-start)/1000+"秒");}}
测试类TestProxy.java
package designMode.proxy;public class TestProxy {public static void main(String[] args) {Tank t = new Tank();TankTimeProxy ttp = new TankTimeProxy(t);TankLogProxy tlp = new TankLogProxy(ttp);Moveable m = tlp;m.move();}}
测试结果
2、动态代理
Spring主要的两大思想,一个是IOC,一个是AOP,对于IOC,就是依赖注入,而对于AOP来说,我们不但要知道怎
么通过AOP来满足我们的功能,更需要知道底层是怎样的原理,而AOP的原理就是Java的动态代理机制。
jdk动态代理和cglib动态代理。两种方法的存在,各有各自的优势。jdk动态代理是由java内部的反射机制来实现的,
cglib动态代理底层则是借助asm来实现。
总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm
生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标
类基于同一的接口。如果没有上述的前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,
cglib这种第三方类库实现的代理应用更加的广泛,且在效率更有优势。
(1)jdk动态代理
在java的动态代理机制中,有两个重要的接口和类。一个是接口InvoactionHandler,一个是类Proxy,这一个类和一个
接口是实现动态代理所必须用到的。
每一个动态代理类都必须实现InvocationHandler这个接口,并且每一个代理类的实例都关联到一个Handler,当我们
通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke方法来调用。
观察InvocationHandler接口的唯一一个方法。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
proxy:代理的真实对象
method:代理的真实对象的某个方法的Method对象
args:调用真实方法对象传递的参数
源码模拟实现:
UserDao.java 接口
package designMode.proxy.proxyJDK.dao;import java.util.Date;/*** * @Description * @author CCQ* @date 2017年6月22日 上午9:28:42**/
public interface UserDao {void save();void delete(int id);void update(int id,String name,String pwd,Date date);
}UserDaoMysqlImpl.java package designMode.proxy.proxyJDK.dao.impl;import java.util.Date;import designMode.proxy.proxyJDK.dao.UserDao;/*** * @Description * @author CCQ* @date 2017年6月22日 下午1:16:41**/
public class UserDaoMysqlImpl implements UserDao {@Overridepublic void save() {System.out.println("Mysql执行保存...");}@Overridepublic void delete(int id) {System.out.println("Mysql执行删除..."+id);}@Overridepublic void update(int id, String name, String pwd, Date date) {System.out.println("Mysql执行修改...id:"+id+"name:"+name+"pwd:"+pwd+"date:"+date);}}UserDaoOracleImpl.javapackage designMode.proxy.proxyJDK.dao.impl;import java.util.Date;import designMode.proxy.proxyJDK.dao.UserDao;public class UserDaoOracleImpl implements UserDao {@Overridepublic void save() {System.out.println("Oracle执行保存...");}@Overridepublic void delete(int id) {System.out.println("Oracle执行删除..."+id);}@Overridepublic void update(int id, String name, String pwd, Date date) {System.out.println("Oracle执行修改...id:"+id+"name:"+name+"pwd:"+pwd+"date:"+date);}}
实现jdk代理第一种形式,使用ProxyFactory工厂的模式生成代理对象
分别有增加事务(getTransactionProxyInstance)和日志(getLogProxyInstance)的代理:
package designMode.proxy.proxyJDK;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** * @Description 创键代理对象工厂* @author CCQ* @date 2017年6月22日 上午9:54:44**/
public class ProxyFactory {private Object obj;public ProxyFactory(Object obj) {super();this.obj = obj;}public Object getTransactionProxyInstance(){Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {/*** 三个参数:1、代理对象,2、目标对象的方法,3、目标对象的参数值列表*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开启事务..."); //执行核心业务之前执行的内容method.invoke(obj, args); //执行目标对象方法,即核心业务System.out.println("关闭事务..."); //执行核心业务之后执行的内容return proxy;}});return proxy;}public Object getLogProxyInstace(){Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {/*** 三个参数:1、代理对象,2、目标对象的方法,3、目标对象的参数值列表*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开启日志..."); //执行核心业务之前执行的内容method.invoke(obj, args); //执行目标对象方法,即核心业务System.out.println("关闭日志..."); //执行核心业务之后执行的内容return proxy;}});return proxy;}
}
第二种是继承InvocationHandler接口,重写invoke方法,提供getInstance方法实现
LogHander.java,增加日志handler
package designMode.proxy.proxyJDK;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class LogHandler implements InvocationHandler {Object target = null;public LogHandler(Object target) {super();this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开启日志..."); //执行核心业务之前执行的内容method.invoke(target, args); //执行目标对象方法,即核心业务System.out.println("关闭日志..."); //执行核心业务之后执行的内容return proxy;}public Object getInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , this);}}
测试类 TestJDKProxy.java
package designMode.proxy.proxyJDK;import java.util.Date;import org.junit.Test;import designMode.proxy.proxyJDK.dao.UserDao;
import designMode.proxy.proxyJDK.dao.impl.UserDaoMysqlImpl;
import designMode.proxy.proxyJDK.dao.impl.UserDaoOracleImpl;/*** * @Description * @author CCQ* @date 2017年6月22日 上午10:02:45**/
public class TestJDKProxy {@Testpublic void test1(){UserDao userDao = new UserDaoMysqlImpl();UserDao userDaoProxy = (UserDao) new ProxyFactory(userDao).getTransactionProxyInstance();userDaoProxy.save();userDaoProxy.update(1, "张三", "123456", new Date());System.out.println("目标对象类型:"+userDao.getClass());System.out.println("代理对象类型:"+userDaoProxy.getClass());}@Testpublic void test2(){UserDao userDao = new UserDaoOracleImpl();UserDao userDaoProxy = (UserDao) new LogHandler(userDao).getInstance();userDaoProxy.save();System.out.println("目标对象类型:"+userDao.getClass());System.out.println("代理对象类型:"+userDaoProxy.getClass());}
}
测试结果:
(2)cglib代理
JDK动态代理机制只能代理实现接口的类,一般没有实现接口的类不能进行代理。cglib就是针对类来实现代理的,它
的原理是对指定目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进
行代理。
使用cglib实现动态代理,完全不受代理类必须实现接口的限制,而且cglib底层采用ASM字节码生成框架,使用字节码
技术生成代理类,比使用java反射效率要高。
需要引入两个jar包:cglib.jar,asm.jar
定义了一个拦截器,在调用目标方法之前,cglib回调MethodInterceptor接口方法拦截,来实现自己的业务逻辑,类似
于JDK中的InvocationHandler接口。
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable ;
proxy:为cglib动态生成的代理实例
method:为上文中实体类所调用的被代理的方法调用
args:为method参数数值列表
methodProxy:为生成代理类对方法的代理引用
返回:从代理实例方法调用返回的值
其中,methodProxy.invokeSuper(obj,arg):
调用代理类实例上的proxy方法的父类方法
UserDaoImpl.java
package designMode.proxy.proxyCglib;/*** * @Description * @author CCQ* @date 2017年6月22日 下午1:16:41**/
public class UserDaoImpl {public void save() {System.out.println("Mysql执行保存...");}}
CglibProxyFactory.java
package designMode.proxy.proxyCglib;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;/*** * @Description * @author CCQ* @date 2017年6月22日 下午1:50:09**/
public class CglibProxyFactory {private Object obj;public CglibProxyFactory(Object obj) {super();this.obj = obj;}public Object getProxyFactory(){//Enhancer类是cglib中的一个字节码增强器,它可以方便的为你所要处理的类进行扩展Enhancer enhancer = new Enhancer();enhancer.setSuperclass(obj.getClass());//将目标对象所在的类作为Enhaner类的父类enhancer.setCallback(new MethodInterceptor() {//通过实现MethodInterceptor实现方法回调@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("事务开启...");method.invoke(obj, args);System.out.println("事务结束...");return proxy;}});return enhancer.create();//生成目标对象并返回}
}
测试类
package designMode.proxy.proxyCglib;import org.junit.Test;/*** * @Description * @author CCQ* @date 2017年6月22日 上午10:02:45**/
public class TestCglibProxy {@Testpublic void test1(){UserDaoImpl userDao = new UserDaoImpl();UserDaoImpl userDaoProxy = (UserDaoImpl) new CglibProxyFactory(userDao).getProxyFactory();userDaoProxy.save();System.out.println("目标对象类型:"+userDao.getClass());System.out.println("代理对象类型:"+userDaoProxy.getClass());}}