下面以添加日志记录为例,分析静态代理的使用。创建一个用户管理类UserManagerImpl,并创建添加用户方法addUser,为其良好扩展性,创建一个通用接口UserManager,代码分别如下:
接口代码:
- package com.snail.pattern;
- public interface UserManager {
- public void addUser(String userId,String userName);
- }
实现类代码:
- package com.snail.pattern;
- public class UserManagerImpl implements UserManager {
- public void addUser(String userId, String userName) {
- try {
- //System.out.println("开始执行");
- System.out.println("HelloWorld!");
- //System.out.println("执行成功!");
- }catch(Exception e) {
- e.printStackTrace();
- //System.out.println("执行失败!");
- throw new RuntimeException();
- }
- }
- }
从代码可以看出,注释里面的日志内容和业务逻辑毫无关系,无形中使耦合性增加,如果很多类中需要添加这些日志代码,工作量不言而喻,修改起来也非常麻烦。如果采用静态代理把打印日志的代码抽取到代理类中,通过代理类和业务逻辑类继承自同一个父类,客户端直接调用代理类完成需求,这样就解决了客户端与业务逻辑类的耦合。示例代码如下:
- package com.snail.pattern;
- public class UserManagerImplProxy implements UserManager{
- private UserManager userManager;
- public UserManagerImplProxy(UserManager userManager){
- this.userManager = userManager;
- }
- @Override
- public void addUser(String userId, String userName) {
- try {
- System.out.println("开始执行");
- userManager.addUser(userId, userName);
- System.out.println("执行成功!");
- }catch(Exception e) {
- e.printStackTrace();
- System.out.println("执行失败!");
- }
- }
- }
客户端调用代码如下:
- package com.snail.pattern;
- public class Client {
- /**
- * @param args
- */
- public static void main(String[] args) {
- UserManager userManager = new UserManagerImplProxy(new UserManagerImpl());
- userManager.addUser("0111", "张三");
- }
- }
静态代理虽隔离了与业务逻辑无关的代码,降低了耦合,让业务逻辑类更专注于业务逻辑,但无法减少代码量,系统重复代码过多,加大了程序员工作量。因此,JDK动态代理完美解决了此问题,动态代理支持在系统运行期给类动态添加代理,然后通过操控代理类完成对目标类的调用。
继续演化上面举的例子,将静态代理改为动态代理,抽象类UserManager和目标类UserManagerImpl中的代码不变,将静态代理类UserManagerImplProxy 删除,添加LoadHandler类,并让它实现InvocationHandler接口中的invoke方法,代码如下:
- package com.snail.pattern;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- public class LogHandler implements InvocationHandler {
- //保留一份targetObject目标类对象
- private Object targetObject;
- //Proxy类动态创建一份目标代理类
- public Object newProxyInstance(Object targetObject){
- this.targetObject = targetObject;
- return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- System.out.println("开始执行!");
- for(int i=0;i<args.length;i++){
- System.out.println(args[i]);
- }
- Object ret = null;
- try{
- //调用目标方法
- ret = method.invoke(targetObject, args);
- System.out.println("执行成功!");
- }catch(Exception e){
- e.printStackTrace();
- System.out.println("执行失败!");
- throw e;
- }
- return ret;
- }
- }
Proxy类所创建的目标类必须实现至少一个接口,在调用newProxyInstance方法时必须与目标类的类加载器和接口一致;invoke方法非常类似Filter中的doFilter方法,它将调用目标类的所有方法在未到达UserManagerImpl之前截获,根据我们自己的需求进行预处理后,继续调用UserManagerImpl。
为了保持invoke方法的通用性,目标方法中的参数以数组args形式传递,如果方法中有返回值,则返回,没有返回值,则返回null。如此一来,程序员不必为每个目标类设计一个代理类,所有需要打印日志的类都可以共用这个LogHandler,如果不想使用日志功能就可以直接删除LogHandler类,对原功能没有丝毫影响,如同揭去显示器上的保护膜,不会影响显示器的使用一般。
客户端调用代码如下:
- package com.snail.pattern;
- public class Client {
- /**
- * @param args
- */
- public static void main(String[] args) {
- LogHandler logHandler = new LogHandler();
- UserManager userManager = (UserManager)logHandler.newProxyInstance(new UserManagerImpl());
- userManager.addUser("id", "name");
- }
- }