- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
本篇文章针对的是对动态代理想要进行深入研究的小伙伴而写,即我们深入源码进行研究
先上一个动态代理的小例子:
public interface IRequest {
public void doGet() throws InterruptedException ;
}
public class Request implements IRequest{
@Override
public void doGet() throws InterruptedException {
System.out.println("处理请求中...");
Thread.sleep(new Random().nextInt(10));
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Request request=new Request();
IRequest proxyInstance = (IRequest)Proxy.newProxyInstance(request.getClass().getClassLoader(),
request.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object invoke = method.invoke(request, args);
long end = System.currentTimeMillis();
System.out.println("请求耗时为 :" + String.valueOf(end - start) + "毫秒");
return invoke;
}
});
proxyInstance.doGet();
}
}
首先,我们利用ProxyGenerator来手动生成上面Request的代理对象,放入到当前项目的target目录下面,这样我们就可以利用编译器自动反编译class文件的功能,而不需要自己手动去反编译了
public class Main {
public static void main(String[] args) throws InterruptedException {
//生成Request类的代理对象
byte[] proxyClass = ProxyGenerator.generateProxyClass("$Proxy0", Request.class.getInterfaces());
//将这个二进制class流写入文件中
String path=System.getProperty("user.dir")+"/target/classes/com/RequestProxy.class";
try(FileOutputStream out=new FileOutputStream(path))
{
out.write(proxyClass);
out.flush();
System.out.println("写入完毕");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
生成的代理对象源码如下:
import com.staticProxy.IRequest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements IRequest {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
//父类有一个InvocationHandler h成员变量
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
//调用 InvocationHandler 的invoke方法,也就是需要我们自己去实现的这个invoke方法
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
//调用 InvocationHandler 的invoke方法,也就是需要我们自己去实现的这个invoke方法
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void doGet() throws InterruptedException {
try {
//调用 InvocationHandler 的invoke方法,也就是需要我们自己去实现的这个invoke方法
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | InterruptedException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
//调用 InvocationHandler 的invoke方法,也就是需要我们自己去实现的这个invoke方法
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
//通过反射提前获取到了所有需要代理对象重写的方法
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
//代理对象只会重写被代理对象实现的接口中的方法,而不会实现被代理对象自身额外添加的方法
m3 = Class.forName("com.staticProxy.IRequest").getMethod("doGet");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我相信看了上面jdk默认生成的代理对象源码,大家肯定对InvocationHandler和Proxy特别感兴趣,下面我们逐个击破,首先是InvocationHandler接口的分析
public interface InvocationHandler {
//代理对象,当前执行的方法,当前方法需要的参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
其实这就是个函数式接口,只有一个需要用户实现的invoke方法。
IRequest proxyInstance = (IRequest)Proxy.newProxyInstance(request.getClass().getClassLoader(),
request.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object invoke = method.invoke(request, args);
long end = System.currentTimeMillis();
System.out.println("请求耗时为 :" + String.valueOf(end - start) + "毫秒");
return invoke;
}
});
在我们通过Proxy对象的newProxyInstance方法创建代理对象的时候,我们会在该方法第三个参数传入InvocationHandler 接口的一个实现类,然后生成的代理对象继承Proxy类,并且通过Proxy类的InvocationHandler类型的成员变量保存了我们传入的InvocationHandler接口的实现类,然后在代理对象执行每个被代理对象方法时,都是通过InvocationHandler的invoke方法执行的
我们来分析一下神秘的newProxyInstance方法:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
//将被代理对象实现的接口数组克隆一份
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//生成代理对象的class---通过传入的类加载器和被代理对象实现的接口数组
//这里还会让代理对象继承Proxy类
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取代理对象的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//生成代理对象的实例,传入InvocationHandler h作为构造器参数
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
Proxy其他注意点:
public class Proxy implements java.io.Serializable {
private static final long serialVersionUID = -2222568056686623797L;
//规定了生成的代理对象构造方法参数类型
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
//会缓存已经生成的代理类
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
//Proxy类中通过h变量保存从构造方法中传入的InvocationHandler对象实例
protected InvocationHandler h;
private Proxy() {
}
//需要传入InvocationHandler的父类构造函数
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
.....
结论: 红色画出的那一行代码会造成死循环,大家思考为什么?
上面说过代理对象的方法调用最终会调用InvocationHandler的invoke方法,那如果我们在InvocationHandler的invoke方法中继续调用代理对象的方法,不就进入一个死循环了吗?
我们这里的本意是通过InvocationHandler的invoke方法对被代理对象的方法进行增强,因此这里method对象关联的实例对象应该是被代理的对象,这里大家不要搞混了。
CGLIB是一个强大的高性能的代码生成包。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承);
利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
Cglib的原理这里不会剖析的特别深入,感兴趣的小伙伴可以自行去查看他的源码,顺便还可以学习一下ASM框架的使用
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public class Request{
public void doGet() throws InterruptedException {
System.out.println("处理请求中...");
Thread.sleep(new Random().nextInt(10));
}
}
//实现MethodInterceptor接口
public class ProxyFactory implements MethodInterceptor
{
//需要代理的目标对象
private Object target;
public ProxyFactory(Object target)
{
this.target=target;
}
//获取代理对象的方法
public Object getProxyInstance()
{
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer=new Enhancer();
// 设置enhancer对象的父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(target.getClass());
//设置enhancer的回调对象
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
/**
* @param o cglib生成的代理对象
* @param method 被代理对象的方法
* @param objects 传入方法的参数
* @param methodProxy 代理的方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
long start = System.currentTimeMillis();
//这里必须传入被代理的对象,否则会死循环
//因为代理对象方法调用会触发拦截器
Object ret= method.invoke(target, objects);
long end = System.currentTimeMillis();
System.out.println("请求耗时为 :" + String.valueOf(end - start) + "毫秒");
return ret;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ProxyFactory proxyFactory=new ProxyFactory(new Request());
Request proxyInstance = (Request) proxyFactory.getProxyInstance();
proxyInstance.doGet();
}
}
这个问题在这里已经有了答案: 关闭10 年前。 Possible Duplicate: Detailed Explanation of Variable Capture in Closures pu
我有一个包含多个 UITextView 的 View ,用户需要点击这些 View 才能开始编辑。但是,在这个 UIView 的横向 View 中,键盘覆盖了这些字段的一半以上。推荐的方法是什么以确保
我目前正在研究我正在开发的网站的用户身份验证协议(protocol)。我想创建一个身份验证 cookie,以便用户可以在页面之间保持登录状态。 这是我的第一个 bash: cookie = user_
在此链接中,您可以看到 Logo 的底部被页面下方的下一个 DIV 中的背景图像挡住了。我认为 z-index 应该控制它,但我很难过。 http://dansdemos.info/prototype
创建工作项(例如 Azure DevOps 中的 Bug)时,您将在“原因”字段的下拉列表中看到的值取决于您为“状态”字段选择的值。例如请参阅这些屏幕截图(模板敏捷,无自定义) 然后,如果您更改状态,
我是一名优秀的程序员,十分优秀!