设计模式真的完美无缺吗?代理模式重磅升级版----动态代理

一提起代理模式相信很多人都会给我这样的答案“代理模式很简单,无非是有两个类(类A和类B),他们实现了同一个接口(或继承同一个抽象类),在类A中拥有类B的引用,当调用类A中方法时实际上调用的是类B中的方法,它屏蔽了真正的实现类,将实现类与客户端解耦”。文字描述起来总是显得那么模糊不清,下面我们通过类图来简单看一下什么是代理模式。


通过上面的类图我们可以很清楚的看到,在客户端所面对的只是一个接口(抽象)这也非常符合我们针对接口(抽象)编程的原则,同时由于代理类的加入,使得客户端和真正实现者之间彻底解耦,设计模式不愧是前人伟大的智慧结晶。但是如果我们根据现实中的真实需求变动来分析一下,代理模式真的是那么完美吗?


新增需求(1):需要改变真正实现结果。比如以前Client调用Proxy中的方法后,Proxy通过调用RealSubject的方法,所实现的效果是给出提示:欢迎您。而现在我需要的效果是给出提示:北京欢迎您。


新增需求(2):记录执行日志。现在不光要调用方法实现效果,而且要记录调用方法的日志。也就是说每当通过Proxy调用了RealSubject的方法后需要记录所调用的方法名称以及调用该方法是否成功。


结合上面的UML图和两个新需求进行分析,如果按照上图中设计模式的方式编程的话,想要满足第一个需求我们有两种解决方案,第一种是更改RealSubject中的实现代码,第二种是新增第二个RealSubject,然后更改代理类中的代码。但是无论是那种方法都不可避免的需要对代码进行改动,严重违反了OCP原则。对于第二个新需求有人可能会说只能在RealSubject实现方法中给出提示了,要是是这样的话,如果RealSubject中一万个方法,那么这样的提示语句我们就要写一万次是吗?好吧,果真是这样的话相信要不了多久你就会被老板请出公司的。说了这么多,难道就没有什么好方法了吗?俗话说”方法总比困难多”,动态代理的横空出世给我们这些成天为代码疯狂的人一个巨大的惊喜。好了,废话不多说下面就来看看java中动态代理的使用是怎样解决这些让人头疼的问题的吧。

首先来介绍一下java中的代理类。在java中凡是实现了InvocationHandler接口的类都有两个方法为newProxyInstance()和invoke(),其中newProxyInstance()方法的作用是返回目标类的代理类,而当调用目标类的方法时首先调用的是代理类的invoke()方法,这个invoke()方法及其类似于前篇文章中介绍过的Filter,在该方法体的前中后分别可以编写目标函数调用前的处理代码,调用目标函数,以及编写调用目标函数后的处理代码。另外,我们完全可以将真正的实现类的配置到配置文件中,一次达到随意更改效果实现的问题。相信看到这你已经被这些枯燥的文字忽悠的迷迷糊糊的了,下面通过代码为你揭开动态代理的神秘面纱,保证你会有一种拨云见日的感觉。代码之前简单画一下UML图帮助你更好的理解代码结构。


有了上面的UML图相信你在理解下面代码的过程中会变得更加得心应手。代码实现过程中提到的dom4j读取xml文件内容的部分请参考《dom4j读取XML文件实现IP数据库连接》一文。废话不多少了,进入期待已久的代码部分:

BaseInvo代码:

package com.bjpowernode.pattern;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import XMLConfig.XMLReader;

public class BaseInvo implements InvocationHandler{
	
	
	protected Object targetObject;
	
	protected Object newProxyInstance(Object targetObject){
		this.targetObject = targetObject;
		XMLReader reader = XMLReader.getInstance();
		InvocationHandler object = null;
		try {
			object = (InvocationHandler)Class.forName(reader.getInstance().getClassPath()).newInstance();		
		} catch (Exception e) {
			e.printStackTrace();
		}
			
		return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), 
				targetObject.getClass().getInterfaces(), this);
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		return null;
	}

}
BaseInvo为继承InvocationHandler的一个类,同时他又是TestInvoAAA和TestInovBBB的父类,经过抽象将原本存在于TestInvoAAA和TestInovBBB中的方法抽取出来供子类继承。 UserManagerImpl类中的代码如下:

package com.bjpowernode.pattern;

public class UserManagerImpl implements UserManager {

	public void addUser(String userId, String userName) {
		//System.out.println("start-->>addUser() userId-->>" + userId);
		try {
			System.out.println("UserManagerImpl.addUser() userId-->>" + userId);
			
			//System.out.println("success-->>addUser()");
		}catch(Exception e) {
			e.printStackTrace();
			//System.out.println("error-->>addUser()");
			throw new RuntimeException();
		}	
	}

	public void delUser(String userId) {
		System.out.println("UserManagerImpl.delUser() userId-->>" + userId);
	}

	public String findUser(String userId) {
		System.out.println("UserManagerImpl.findUser() userId-->>" + userId);
		return "张三";
	}

	public void modifyUser(String userId, String userName) {
		System.out.println("UserManagerImpl.modifyUser() userId-->>" + userId);
	}
}

该类即为上文中提到的目标类,在该类中存在真正的Client端想要调用的方法,而代理类也正是为该类进行代理。该类实现UserManager接口,由于在接口中只存在方法的声明,所以在此就不再写出接口中的代码了。
TestInvoAAA类中的代码:

package com.bjpowernode.pattern;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.sun.xml.internal.org.jvnet.fastinfoset.sax.ExtendedContentHandler;

public class TestInvoAAA extends BaseInvo implements InvocationHandler  {
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("我是testInvoAAA类的invoke方法");
		
		Object mObject = null ;
		try{
			//调用目标方法
			mObject = method.invoke(targetObject, args);
			System.out.println("sucess----->>TestInvoAAA" + method.getName());
		}catch(Exception e){
			e.printStackTrace();
			System.out.println("error----->>" + method.getName());
			throw e;
		}
		return mObject;
	}
}

TestInovBBB类中的代码:

package com.bjpowernode.pattern;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TestInvoBBB extends BaseInvo implements InvocationHandler {

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("我是testInvoBBB类的invoke方法");
		
		Object mObject = null ;
		try{
			//调用目标方法
			mObject = method.invoke(targetObject, args);
			System.out.println("sucess----->>TestInvoBBB" + method.getName());
		}catch(Exception e){
			e.printStackTrace();
			System.out.println("error----->>" + method.getName());
			throw e;
		}
		return mObject;
	}

}

TestInoAAA和TestInvoBBB才是目标类UserManagerImpl真正的代理类,也就是由它对调用UserManagerImpl类中方法前后进行处理,实现上文中提到的日志记录功能。这两个类也是我们配置在配置文件中的类,也正是由于具体实例化哪个类是由配置文件中的配置决定的,所以当我们要更改实现效果时不必改动一个代码,只需要更改配置文件中的实现类,就可以达到目的。下面我们简单来看一下配置文件中的内容:

<?xml version="1.0" encoding="UTF-8"?>
<config>
	<dynamic_classpath>com.bjpowernode.pattern.TestInvoAAA</dynamic_classpath>
</config>

该配置文件中的内容即为需要实例化的具体的代理类,通过更改此处,可以在不修改代码的情况下随意更改代理类实现不同处理效果。最后来看客户端的代码:

package com.bjpowernode.pattern;

import java.lang.reflect.InvocationHandler;

import XMLConfig.XMLReader;

import com.sun.org.apache.bcel.internal.generic.NEW;

public class Client {
	public static void main(String[] args){
		
		XMLReader reader = XMLReader.getInstance();
		BaseInvo realInvocationHandler = null;
		try {
			realInvocationHandler = (BaseInvo)Class.forName(reader.getClassPath()).newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		UserManager userManager = (UserManager)realInvocationHandler.newProxyInstance(new UserManagerImpl());
		userManager.delUser("0001");
	}
	
	
}


在客户端中通过配置文件动态加载代理类,下面以实例化TestInvoAAA类为例来解读动态代理具体的工作流程。从最前端的Client代码中我们可以看到客户端首先new了一个XMLReader对象,该对象是用来读取配置文件中的内容决定实例化哪个代理类的,具体使用方法请点击《 dom4j读取XML文件实现数据库连接》。接下来进行的是具体的代理类的实例化操作,这个就不用我多少了吧。再来看倒数第二行代码,这行代码是用来指定代理类的,说白了也就是说给UserManagerImpl指定代理,这个代理类就是前面实例化出的realInvocationHandler。最后一行代码很简单是调用目标对象的delUser方法,但是由于是制定了代理类的,所以在调用目标函数的del方法后并不是直接进入该函数的方法体,而是首先调用了代理类的Invoke方法,由于我们是以TestInvoAAA为例,所以首先跳入的是TestInvoAAA的invoke方法体,下面请把目光转入TestInvoAAA的invoke方法。在该方法中首先输出了一行文字,其实这里相当于执行目标函数前进行的处理操作,接下来便是调用真正的目标函数方法,在执行完目标函数之后返回执行结果。当然我们也完全可以在执行目标函数之后和返回执行结果中间加入代码进行再一次处理,这次处理就是执行目标函数之后的处理。总的感觉来讲,动态代理更像是前面文章中提到过的Filter(点击《 Filter技术实现Ip访问权限验证》了解Filter的执行过程和机理),其实就是一个执行前干什么,执行,执行后干什么的过滤器。有了动态代理相信再解决上述问题将会轻而易举。






  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值