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,运行结果
结果表明:代理对象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,运行结果:
二:动态代理
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");
}
}
输出结果如下
可以看到,对该类的每个方法均进行了动态代理。
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();
}
}
}
结果截图如下:
2,Spring的动态代理
1,cglib通过asm(开源的操作字节码的操作)