代理模式-指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,属于结构型设计模式。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
简而言之代理模式就是对目标对象做个封装,以起到隐藏目标对象(保护目标对象),和在目标对象进行的逻辑处理之外,增加一些逻辑处理的功能(增强目标对象)。
主要分为静态代理和动态代理!其中,动态代理又有JDKProxy和CGLib两种主要的动态代理方式
静态代理实例--
显式声明被代理对象
当今社会,年轻人一般都没有时间去找对象,那么心急的父母就会作为孩子的代理,去帮孩子找对象,在这里,孩子就是被代理对象。
首先创建一个Person接口,里面定义了一个findLove方法:
public interface Person { public void findLove(); }
再创建一个Son类,它去 实现Person接口:
public class Son implements Person{ @Override public void findLove(){ System.out.println("儿子要求:肤白、貌美大长腿"); } }
再创建一个Father类,在它里面显式声明了被代理对象(即Son类的对象),并显式的定义了一个带参构造函数,然后也定义一个findLove方法,并在里面调用Son的findLove方法:
public class Father{ //显示声明被代理对象 private Person person; public Father(Person person){ this.person = person; } public void findLove(){ System.out.println("父亲物色对象"); this.person.findLove(); System.out.println("双方父母同意,确立关系"); } }
来看测试类FatherProxyTest:
public class FatherProxyTest { public static void main(String[] args) { //只能帮儿子找对象,这就是静态代理 Father father = new Father(new Son()); father.findLove(); } }
在测试类里面,通过Father类的构造函数,得到了一个Son类的对象,并且执行了Son类里的findLove方法,这就是静态代理模式的一个简单例子。
再看一个实际的业务场景,在分布式业务场景中,我们通常会对数据库进行分库分表,分库分表之后使用Java操作时,就可能需要配置多个数据源,我们可以实现根据订单创建时间来动态切换数据源(不是动态代理)。
首先来创建Order订单实体:
//创建order订单实体 public class Order { private Object orderinfo; //订单创建时间按年分库 private long createTime; private String id; public Object getOrderinfo() { return orderinfo; } public void setOrderinfo(Object orderinfo) { this.orderinfo = orderinfo; } public long getCreateTime() { return createTime; } public void setCreateTime(long createTime) { this.createTime = createTime; } public String getId() { return id; } public void setId(String id) { this.id = id; } }
创建OrderDao持久层操作,里面就是一些逻辑操作:
//创建OrderDao持久层操作 public class OrderDao { public int insert(Order order){ System.out.println("OrderDao创建order成功"); return 1; } }
创建IOrderService接口:
//创建IOrderService接口 public interface IOrderService { int createOrder(Order order); }
创建OrderService实现类:
public class OrderService implements IOrderService {
//静态代理的标志,显式声明被代理对象 private OrderDao orderDao; public OrderService(){ //如果使用spring应该是自动注入的 //我们为了使用方便,在构造方法中直接将OrderDao初始化了 orderDao = new OrderDao(); } @Override public int createOrder(Order order) { System.out.println("OrderService调用orderDao创建订单"); // TODO Auto-generated method stub return orderDao.insert(order); } }
然后使用静态代理,根据订单创建时间自动按年进行分库。
先创建数据源路由对象,使用ThreadLocal单例实现,DynamicDataSourceEntry类:
//动态切换数据源 public class DynamicDataSourceEntity { //默认数据源 public final static String DEFAULE_SOURCE = null; private final static ThreadLocal<String> local = new ThreadLocal<String>(); //构造方法私有化,即单例模式 private DynamicDataSourceEntity(){} //清空数据源 public static void clear(){ local.remove(); } //获取当前正在使用的数据源名字 public static String get(){ return local.get(); } //还原当前切面的数据源 public static void restore(){ local.set(DEFAULE_SOURCE); } //DB_2018 //DB_2019 //设置已知名字的数据源 public static void set(String source){ local.set(source); } //根据名字动态设置数据源 public static void set(int year){ local.set("DB_" + year); } }
创建切换数据源的代理OrderServiceStaicProxy类,这里的被代理类就是IOrderService接口:
//代理数据源切换的逻辑 public class OrderServiceStaticProxy implements IOrderService { private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); //因为只能代理OrderService的对象,所以是静态代理。 //静态代理要求你的对象已经创建好了 private IOrderService orderService; public OrderServiceStaticProxy(IOrderService orderService){ this.orderService = orderService; } public int createOrder(Order order){ Long time = order.getCreateTime(); Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time))); System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据"); DynamicDataSourceEntity.set(dbRouter); this.orderService.createOrder(order); DynamicDataSourceEntity.restore(); return 0; } }
测试类,这里OrderServiceStaticProxy代理类的参数是OrderService类的对象,因为接口的对象只能通过其实现类实现!:
public class DbRouteProxyTest { public static void main(String[] args) { // TODO Auto-generated method stub try{ Order order = new Order(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); Date date = sdf.parse("2017/02/01"); order.setCreateTime(date.getTime()); //接口的对象是只能通过其实现类实现,其对象调用的方法也是其实现类的方法。 //通过静态代理分配数据源。 IOrderService orderService = new OrderServiceStaticProxy(new OrderService()); orderService.createOrder(order); }catch(Exception e){ e.printStackTrace(); } } }
动态代理示例--
动态代理和静态代理基本思路一致,但是动态代理功能更强大,随着业务的扩展适应性更强。即动态代理把代理对象的限制取消,可以代理任何对象!
还以找对象为例,静态代理是父母给儿子找对象,而且父母只会给自己的儿子找对象,但是动态代理把找对象发展成一个产业,相当于媒婆、婚介所之类的,可以给任何有需求的人找对象。
动态配置和替换被代理对象。
动态代理主要有两种流行的实现方式:JDK动态代理和CGLib动态代理。
先看动态代理的例子
首先,创建一个Girl类,也让它去实现Person接口:
//对于被代理对象来说,它也必须要有一个接口! public class Girl implements Person { @Override public void findLove() { // TODO Auto-generated method stub System.out.println("高富帅"); System.out.println("身高180厘米"); System.out.println("有6块腹肌"); } }
为什么要实现这个接口?其实静态代理是不必实现这个接口的,但是如果使用JDK动态代理的话,就必须要实现一个接口!原因会在下面讲。
再创建一个JDKMeipo类,它作为代理类去代理Girl对象:
//必须实现InvocationHandler接口 public class JDKMeipo implements InvocationHandler { //要持有被代理对象的引用 private Object target; //提供一个注入的方法 public Object getInstance(Object target) throws Exception{ this.target = target; //把类保存下来 Class<?> clazz = target.getClass(); //jdk的写法 return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub before(); //用到了java反射 Object obj = method.invoke(this.target,args); after(); return obj; } private void before(){ System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求"); System.out.println("开始物色"); } private void after(){ System.out.println("OK的话,准备办事"); } }
可以看出,要使用JDK动态代理的话,代理类必须实现InvocationHandler接口!并且要提供一个注入的方法getInstance方法,getInstance是不通过构造函数获取对象的一种手段。这个方法里面的代码基本上是一样的,里面通过泛型调用了getInterfaces方法,这里和为什么被代理类一定要实现一个接口有关系。
还重写了Invoke方法,可以看出JDK动态代理是通过Java反射实现的,基本上也是一样的写法。
还实现了before方法和after方法,这两个方法是用来实现目标对象增强的。就是在调用目标对象的逻辑前后执行的逻辑。
调用方法:
public class JDKProxyTest { public static void main(String[] args) { try{ //在动态代理中,媒婆可以为任何人找对象 //这个obj已经是JDK自动生成的类的对象了 Object obj = new JDKMeipo().getInstance(new Girl()); Method method = obj.getClass().getMethod("findLove", null); method.invoke(obj); //这段代码是输出$Proxy0.class文件 byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class}); FileOutputStream os = new FileOutputStream("E://$Proxy0.class"); os.write(bytes); os.close(); }catch(Exception e){ e.printStackTrace(); } } }
在这段代码中,可以看到不再需要显式声明被代理对象了,直接通过JDKMeipo的getInstance方法,传入被代理Girl类的对象,通过反射去执行findLove方法。但是这里有个疑问,如果仅是如此的话,那么之前的before和after方法就不会被执行,但是事实上,这两个方法的逻辑都被执行了!
可以通过debug啥的看出调用JDKMeipo得到的obj对象并不是某个已知类的对象,反而是一个叫做$Proxy0的类的对象,我们可以通过上面的一段代码去把这个类的class文件输出出来,反编译后可以看到这个类里面也有个findLove方法,事实上,obj其实是这个 类的对象,这就引出了JDKProxy的实现原理——
JDK动态代理,是利用动态生成字节码的原理,在代理中生成它($Proxy0)的源代码,然后编译成class文件,再通过classloader加载它,然后执行,也就是说,Java反射调用的类完全是一个新的类,里面调用了JDKMeipo类里重写的invoke方法,所以before和after方法才会被执行到!
这也是为什么被代理类要实现一个接口,因为需要根据这个接口找到这个接口里 方法,实现并覆盖到被代理类的方法里,并且新生成的方法是不可再次被重写的(final类型的),被代理的对象也不能被二次代理了,所以要把需要代理的方法写到接口里,才能被扫描到!
现在可以用动态代理去实现动态分配数据源了。
创建动态代理的类OrderServiceDynamicProxy:
public class OrderServiceDynamicProxy implements InvocationHandler{ private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); Object proxyobj; //动态代理的对象可以是任何对象了 public Object getInstance(Object proxyobj){ this.proxyobj = proxyobj; Class<?> clazz = proxyobj.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } //args是参数列表,可以获取被代理对象的参数 public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{ before(args[0]); Object object = method.invoke(proxyobj, args); after(); return null; } private void before(Object target){ try{ //进行数据源的切换 System.out.println("Proxy before method"); //约定优于配置 //也就是说接口和实现类必须要有"getCreateTime"方法 Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target); Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time))); System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据"); DynamicDataSourceEntity.set(dbRouter); }catch(Exception e){ e.printStackTrace(); } } private void after(){ System.out.println("Proxy after method"); //还原成默认的数据源 DynamicDataSourceEntity.restore(); } }
测试代码:
public class DbRouteDynamicProxyTest { public static void main(String[] args) { try{ Order order = new Order(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy/mm/dd"); Date date = sdf.parse("2017/02/01"); order.setCreateTime(date.getTime()); IOrderService orderService = (IOrderService) new OrderServiceDynamicProxy().getInstance(new OrderService()); orderService.createOrder(order); }catch(Exception e){ e.printStackTrace(); } } }
然后再看看CGLib动态代理的使用--
新建一个Customer类:
public class Customer { public void findLove(){ System.out.println("儿子要求:肤白貌美大长腿"); } }
创建CglibMeipo类:
public class CglibMeipo implements MethodInterceptor { //把代理的引用传进来 //为什么要传Class?是因为要通过动态生成源代码去继承传来的这个Class public Object getInstance(Class<?> clazz) throws Exception{ //相当于Proxy,代理的工具类 Enhancer enhancer = new Enhancer(); //设置父类就是传来的这个clazz enhancer.setSuperclass(clazz); //回调函数,回调下面的intercept方法 enhancer.setCallback(this); return enhancer.create(); } @Override //在这个方法里实现代理的增强 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // TODO Auto-generated method stub before(); //生成的类是子类,目的是实现父类的的一些方法 //在子类中实现父类的增强,调用父类的逻辑 Object obj = methodProxy.invokeSuper(o, objects); after(); return obj; } private void before(){ System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求"); System.out.println("开始物色"); } private void after(){ System.out.println("OK的话,准备办事"); } }
测试类:
public class CglibTest { public static void main(String[] args) { try{ //JDK是采用读取接口的信息 //CGlib是覆盖父类方法 //目的:都是生成一个新的类,去实现增强代码逻辑的功能 //JDK Proxy对于用户而言,必须要有一个接口实现,目标类相对来说复杂 //CGlib可以代理任意一个普通的类,没有任何的要求 //CGlib生成代理逻辑更复杂,调用效率更高,生成一个包含了所有逻辑的FastClass,不再需要反射调用 //JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用 //CGlib有个坑,CGlib不能代理final的方法 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes"); Customer obj = (Customer) new CglibMeipo().getInstance(Customer.class); obj.findLove(); }catch(Exception e){ e.printStackTrace(); } } }
可以看出,CGLib代理的对象不需要实现任何接口,它是通过动态继承目标对象实现的动态代理,也是自动生成新的类,不过是会生成三个。
通过上面的System.setProperty方法,可以把新生成的类输出到磁盘上。
但是CGLib不能代理final修饰的方法!因为final类型的方法不能被重写了!
动态代理总结--
可以利用动态代理做日志监听和AOP切面编程。
JDK动态代理的实现原理:
1.拿到被代理类的引用,并且获取它所有的接口(反射获取)。
2.JDK Proxy重新生成一个新的类,实现了被代理类所有接口的方法。
3.动态生成Java代码,把增强逻辑加入到新生代码中。
4.编译生成新的Java代码的class文件。
5.加载并重新运行新的class,得到的类就是全新类。
CGLib和JDK动态代理对比:
1.JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象。
2.JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib是使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK效率低,
3.JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。
代理模式的优点:
代理模式能将代理对象和真实被调用的对象分离,一定程度上降低了系统的耦合性,易于扩展。可以起到保护目标对象的作用。可以增强目标对象的职责。
代理模式的缺点:
会造成系统中类的数目增多。在客户端和目标对象之间增加了一个代理对象,会造成请求处理速度变慢。增加了系统的复杂度。
Spring中的代理选择原则:
1.当bean有实现接口时,Spring就会用JDK的动态代理。
2.当bean没有实现接口时,Spring选择CGLib。
3.Spring可以通过配置强制使用CGLib,只需要在Spring的配置文件中加入如下代码——<aop:aspectj-autoproxy proxy-target-class="true"/>