Java中的代理(Proxy)

设计模式--Proxy

增强一个对象的功能

买火车票,app就是一个代理,他代理了火车站,小区当中的代售窗口

java当中如何实现代理

一,java实现的代理的两种办法(静态代理和动态代理)

1,继承

代理对象继承目标对象,重写需要增强的方法;

缺点:会代理类过多,非常复杂

1,新建一个接口(LubanDao 接口)

package com.huaxu.dao;

public interface LubanDao {

    public void query();

    public String queryLog();

}

2,新建一个目标对象(UserDaoImpl 类)

package com.huaxu.dao;

public class LubanDaoImpl implements LubanDao{

    @Override
    public void query(){
        System.out.println("目标对象:query");
    }

    @Override
    public String queryLog() {
        return "目标对象: queryLog";
    }

}

3,新建一个增强后的代理对象(LubanDaoImplProxy)

package com.huaxu.dao;

/*
 *   @author Dongxu Hua
 *   @date 2019/4/6 15:50
 */
public class LubanDaoImplProxy  extends LubanDaoImpl{

   @Override
   public void query(){
      System.out.println("代理对象:对目标对象进行增强:log ----");
      super.query();
   }
}

4,新建一个test,测试代码。

package com.huaxu.test;

import com.huaxu.dao.LubanDao;
import com.huaxu.dao.LubanDaoImplProxy;


public class Test {
    public static void main(String[] args) {

        /**
         * 自己手写的动态代理
         */
        // 1, 增强
        LubanDao dao = new LubanDaoImplProxy();
        dao.query();


        /**
         * jdk 动态代理
         */
        /*LubanDao jdkProxy = (LubanDao)                 
        Proxy.newProxyInstance(Test.class.getClassLoader() ,
                new Class[]{LubanDao.class} , new HuaxuInvocationHandle(new 
        LubanDaoImpl()));

        jdkProxy.query();

        jdkProxy.queryLog();*/
    }
}

5,运行结果

Java中的代理(Proxy)

结果表明:代理对象new LubanDaoImplProxy() 对 目标对象 new UserDaoImpl() 的方法进行了增强。需要增强的方法可以自己定义。因为每一个代理对象都必须要对目标对象进行增强。所以很容易产生类爆炸。

2,聚合

目标对象和代理对象实现同一个接口,代理对象当中要包含目标对象。

缺点:也会产生类爆炸,只不过比继承少一点点

总结:如果在不确定的情况下,尽量不要去使用静态代理。因为一旦你写代码,就会产生类,一旦产生类就爆炸。

1,新建一个接口(UserDao接口)

package com.huaxu.dao;

public interface UserDao {
    public void query();
    public void query(String p);
}

2,新建一个目标对象(UserDaoImpl),实现接口UserDao。 

package com.huaxu.dao;

public class UserDaoImpl implements UserDao{

    public void query(){
        System.out.println("目标对象:::假装User正在查询查询数据库");
    }

    public void query(String aa){

        System.out.println("目标对象执行::"+aa);
    }
}

3,新建一个代理对象(UserDaoImplProxy)

package com.huaxu.dao;

/*
 *   @author Dongxu Hua
 *   @date 2019/4/6 16:38
 */
public class UserDaoImplProxy implements UserDao {

	private UserDao target;

	public UserDaoImplProxy(UserDaoImpl target) {
		this.target = target;
	}

	@Override
	public void query() {
		System.out.println("代理对象执行:-----》");
		target.query();
	}

	@Override
	public void query(String p) {

	}
}

4,测试

package com.huaxu.test;

import com.huaxu.dao.LubanDao;
import com.huaxu.dao.LubanDaoImplProxy;
import com.huaxu.dao.UserDaoImpl;
import com.huaxu.dao.UserDaoImplProxy;


public class Test {
    public static void main(String[] args) {

        /**
         * 静态代理: 基于继承
         */
        /*LubanDao dao = new LubanDaoImplProxy();
        dao.query();*/

        /**
         * 基于聚合
         */
        UserDaoImpl target = new UserDaoImpl();
        UserDaoImplProxy proxy = new UserDaoImplProxy(target);
        proxy.query();

        /**
         * jdk 动态代理
         */
        /*LubanDao jdkProxy = (LubanDao) Proxy.newProxyInstance(Test.class.getClassLoader() ,
                new Class[]{LubanDao.class} , new HuaxuInvocationHandle(new LubanDaoImpl()));

        jdkProxy.query();

        jdkProxy.queryLog();*/
    }
}
5,运行结果:

Java中的代理(Proxy)

 

二:动态代理

 

1,JDK动态代理

1,先来看看jdk中对Proxy类的 newProxyInstance 这个方法的说明

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.

     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

下面是对这个方法传入参数的解释。

Returns an instance of a proxy class for the specified interfaces
that dispatches method invocations to the specified invocation
handler.


<p>{@code Proxy.newProxyInstance} throws
{@code IllegalArgumentException} for the same reasons that
{@code Proxy.getProxyClass} does.

@param   loader the class loader to define the proxy class
第一个参数:类加载器:

@param   interfaces the list of interfaces for the proxy class
          to implement
第二个参数:接口代理类的接口列表

@param   h the invocation handler to dispatch method invocations to
第三个参数: h调用处理程序将方法调用分派到

用法例子一:

1,新建一个代理类(InvocationHandlerService),实现 InvocationHandler 接口

package com.huaxu.test;

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

/*
 *   @author Dongxu Hua
 *   @date 2019/4/6 14:52
 */
public class InvocationHandlerService implements InvocationHandler {
    
    //	真实对象
	private Object target;
    
    
	public InvocationHandlerService(Object target){
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("我做了动态代理!!!");
		System.out.println("method:"+ method.getName() + "----->is invoked!");
		method.invoke(target , args);
		System.out.println("invoke end");
		return null;
	}
}

在代理目标对象方法执行的时候,前后对方法做了增强。对 UserDaoImpl 类做增强。

测试代码如下:

 

package com.huaxu.util;

import com.huaxu.dao.UserDao;
import com.huaxu.dao.UserDaoImpl;
import com.huaxu.test.InvocationHandlerService;

import java.lang.reflect.Proxy;

public class Test {
	public static void main(String[] args) {
		/**
		 * 这里其实就是聚合的代理方式
		 */
		InvocationHandlerService invocationHandlerService = new InvocationHandlerService(new UserDaoImpl());
		/**
		 * 以下代码会生成一个$Proxy对象,该对象实现了ITargetClass,具体的方法体就是InvocationHandler里面的invoke方法。
		 */
		UserDao userDao = (UserDao) Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader() ,
				UserDaoImpl.class.getInterfaces() , invocationHandlerService);
		userDao.query();
		userDao.query("huaxu");
	}
}

输出结果如下

Java中的代理(Proxy)

可以看到,对该类的每个方法均进行了动态代理。

2,自己手写一个山寨版的jdk动态代理

 1,新建一个接口命名为:CustomInvocationHandler 就类似于jdk代码中的 InvocationHandler

package com.huaxu.proxy;

import java.lang.reflect.Method;

/*
 *   @author Dongxu Hua
 *   这个类就类似于 , jdk 中的 InvocationHandler的那个接口。只是少了参数。
 *   @date 2019/4/6 10:05
 */
public interface CustomInvocationHandler {

	public Object invoke(Method method);

}

2,写一个类 TestCustomHandler 去实现 CustomInvocationHandler这个接口。代码如下

 

package com.huaxu.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/*
 *   @author Dongxu Hua
 *   @date 2019/4/6 23:39
 */
public class TestCustomHandler implements CustomInvocationHandler{

	Object target;
	public TestCustomHandler(Object target){
		this.target=target;
	}

	@Override
	public Object invoke(Method method) {
		try {
			System.out.println("--------before--------");
			return  method.invoke(target);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}

}

对代理对象做了  ----- before -----增强  

3,书写 ProxyUtils 仿写于 JDK 中的 Proxy类

package com.huaxu.proxy;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.Exception;

/*
 *   @author Dongxu Hua
 *   @date 2019/4/2 22:57
 */
public class ProxyUtils {

	public static Object newInstance(Class targetInf, CustomInvocationHandler h) {
		Object proxy = null;
		//String handlerName = CoustomInvocationHandler.class.
		Method methods[] = targetInf.getDeclaredMethods();
		String line = "\n";
		String tab = "\t";
		String infName = targetInf.getSimpleName();
		String content = "";
		String packageContent = "package com.google;" + line;
		String importContent = "import " + targetInf.getName() + ";" + line
				+ "import com.huaxu.proxy.CustomInvocationHandler;" + line
				+ "import java.lang.Exception;"
				+ "import java.lang.reflect.Method;" + line;
		String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
		String filedContent = tab + "private CustomInvocationHandler h;" + line;
		String constructorContent = tab + "public $Proxy (CustomInvocationHandler h){" + line
				+ tab + tab + "this.h =h;"
				+ line + tab + "}" + line;
		String methodContent = "";
		for (Method method : methods) {
			String returnTypeName = method.getReturnType().getSimpleName();
			String methodName = method.getName();
			// Sting.class String.class
			Class args[] = method.getParameterTypes();
			String argsContent = "";
			String paramsContent = "";
			int flag = 0;
			for (Class arg : args) {
				String temp = arg.getSimpleName();
				//String
				//String p0,Sting p1,
				argsContent += temp + " p" + flag + ",";
				paramsContent += "p" + flag + ",";
				flag++;
			}
			if (argsContent.length() > 0) {
				argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
				paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
			}

			methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ")throws Exception {" + line
					+ tab + tab + "Method method = Class.forName(\"" + targetInf.getName() + "\").getDeclaredMethod(\"" + methodName + "\");" + line;
					if("void".equals(returnTypeName)){
						methodContent +="h.invoke(method);";
					}else{
						methodContent += tab + tab + "return (" + returnTypeName + ")h.invoke(method);" + line;
					}
			methodContent += tab + "}" + line;
		}

		content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";

		File file = new File("d:\\com\\google\\$Proxy.java");
		try {
			if (!file.exists()) {
				file.createNewFile();
			}

			FileWriter fw = new FileWriter(file);
			fw.write(content);
			fw.flush();
			fw.close();


			JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

			StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
			Iterable units = fileMgr.getJavaFileObjects(file);

			JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
			t.call();
			fileMgr.close();

			URL[] urls = new URL[]{new URL("file:D:\\\\")};
			URLClassLoader urlClassLoader = new URLClassLoader(urls);
			Class clazz = urlClassLoader.loadClass("com.google.$Proxy");

			Constructor constructor = clazz.getConstructor(CustomInvocationHandler.class);
			proxy = constructor.newInstance(h);
			//clazz.newInstance();
			//Class.forName()
		} catch (Exception e) {
			e.printStackTrace();
		}


		/**
		 * public UserDaoLog(UserDao target){
		 * 		this.target =target;
		 *
		 *        }
		 */
		return proxy;

	}
}

PS:通过JavaCompiler这个类生成对应的代码文件,可以方便对比,我这里把代码放在了D盘目录下面。

package com.google;
import com.huaxu.dao.UserDao;
import com.huaxu.proxy.CustomInvocationHandler;
import java.lang.Exception;import java.lang.reflect.Method;
public class $Proxy implements UserDao{
	private CustomInvocationHandler h;
	public $Proxy (CustomInvocationHandler h){
		this.h =h;
	}
	public void query()throws Exception {
		Method method = Class.forName("com.huaxu.dao.UserDao").getDeclaredMethod("query");
		h.invoke(method);	
	}
}

 PS:这个是生成的java代码:可以很清晰的看到,jdk的proxy类中的代理方式就是用的聚合。通过反射到接口,然后调用方法。

3,测试Test类代码:

package com.huaxu.test;

import com.huaxu.dao.UserDao;
import com.huaxu.dao.UserDaoImpl;
import com.huaxu.proxy.ProxyUtils;
import com.huaxu.proxy.TestCustomHandler;
import sun.misc.ProxyGenerator;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
 *   @author Dongxu Hua
 *   @date 2019/4/2 22:55
 */
public class Test1 {

	public static void main(String[] args) {
		/*TestCustomHandler testCustomHandler = new TestCustomHandler(new LubanDaoImpl());
		UserDao proxy = (UserDaoImpl) ProxyUtils.newInstance(UserDao.class , testCustomHandler );
		proxy.query();*/
		byte[] bytes= ProxyGenerator.generateProxyClass("$Proxy18",new Class[]{UserDao.class});

		try {
			FileOutputStream fileOutputStream = new FileOutputStream("d:\\$Proxy18.class");
			fileOutputStream.write(bytes);
			fileOutputStream.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		TestCustomHandler testCustomHandler = new TestCustomHandler(new UserDaoImpl());

		UserDao jdkproxy = (UserDao) ProxyUtils.newInstance(UserDao.class, testCustomHandler );

		try {
			jdkproxy.query();
          } catch (Exception e) {
              e.printStackTrace();
          }


	}
}

结果截图如下:

Java中的代理(Proxy) 

2,Spring的动态代理

1,cglib通过asm(开源的操作字节码的操作)