Java随笔(五)

与之前写的Java随笔一样,此篇博客主要是用来记录我之前存放在本地的Word文档中的一些Java的自身理解,由于水平有限所以可能只适合自己加深理解与记忆。


目录

使用接口的类的动态代理实现

未使用接口的类的动态代理实现

使用Cglib实现不同方法进行不同代理


本篇博客中,主要是介绍动态代理的两种方式。

动态代理的实现方式,我学习了两种,一种是基于java.lang.reflect.InvocationHandler的,另外一种是基于Cglib的,cglib需要引入对应的jar包。Spring中也是使用了这两种代理方式。

如果被代理的类实现了接口,那么使用InvocationHandler相关的类可以实现动态代理,而如果被代理的类并没有实现接口,那么就要使用Cglib来实现了。

使用接口的类的动态代理实现

接口类:

package com.zhl.mytest;

public interface Student {
	public void sayHello();
}

实现类:

package com.zhl.mytest;

public class RealStudent implements Student{
	public void sayHello(){
		System.out.println("Hello!");
	}
}

如上,假如我们想实现动态代理RealStudent 可以使用如下方式。

package com.zhl.mytest;

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


public class Test implements InvocationHandler{

	private Object obj;
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("Before Invoke");
		//执行方法之前做一些操作
		Object returnObj = method.invoke(obj, args);
		//执行方法之后做一些操作
		System.out.println("After Invoke");
		return returnObj;
	}
	public Object bind(Object obj){
		this.obj = obj;
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
				obj.getClass().getInterfaces(), this);

	}
	public static void main(String[] args) {
		Test test = new Test();
		Student stu = (Student) test.bind(new RealStudent());
		stu.sayHello();
	}

}

可以看到 我们一个类 实现了InvocationHandler接口,并且实现了invoke函数,在里面我们可以在调用 代理对象方法 之前和之后做一些操作。Obj保存 被代理类的对象,然后使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)获取到 代理后的对象, 方法参数 为  类加载器,被代理类实现的接口类数据,以及代理处理的InvocationHandler 对象,这里也就是Test类的实例对象。将获得的对象转为 想要的接口的实现类,就能跟使用 被代理类的对象一样  使用被增强的类的对象,如果想处理不同的方法,还能在invoke中 判断Method 来判定是否要代理之类的。

未使用接口的类的动态代理实现

假设我们有下面这样一个需要被代理的类:

package com.zhl.mytest;

public class RealStudent {
	public void sayHello(){
		System.out.println("Hello!");
	}
public void sayHelloTwo(){
		System.out.println("Hello Two!");
	}

}

因为它没有实现接口,所以不能用 代理类实现InvocationHandler 接口然后 Proxy.newProxyInstance方法 来获取代理类,因为 第二个参数被代理类的接口是空的。如果强行将获得的对象转为RealStudent 会报错:

所以要使用Cglib的方式,如下:

package com.zhl.practice.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class RealStudentProxy implements MethodInterceptor{

	@Override
	public Object intercept(Object object, Method method, Object[] objects, 
MethodProxy methodProxy) throws Throwable {
		//调用方法之前进行一些处理
		System.out.println("Before Invoke!");
		Object returnObj = methodProxy.invokeSuper(object, objects);
		//调用方法之后进行一些处理
		System.out.println("After Invoke!");
		return returnObj;
	}

}

然后调用方式如下:

package com.zhl.practice.cglib.test;

import net.sf.cglib.proxy.Enhancer;

import org.junit.Test;

import com.zhl.mytest.RealStudent;
import com.zhl.practice.cglib.RealStudentProxy;

public class CglibTest {
	@Test
	public void testCglib(){
		RealStudentProxy studentProxy = new RealStudentProxy();
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(RealStudent.class);
		enhancer.setCallback(studentProxy);
		RealStudent reStudent = (RealStudent) enhancer.create();
		reStudent.sayHello();
	}
}

使用Enhancer 将 代理类 和 被代理类 联系在一起 然后 使用enhancer.create() 获取到增强后的 类的对象,从而实现了动态代理。Cglib是使用ASM来动态修改byteCode来实现的动态代理。

使用Cglib实现不同方法进行不同代理

如果想对不同的方法进行不同的拦截可以使用如下代码

首先定义另外一个代理类:

package com.zhl.practice.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class AnotherRealStudentProxy implements MethodInterceptor{
	public Object delegate;
	
	@Override
	public Object intercept(Object object, Method method, Object[] objects,
			MethodProxy methodProxy) throws Throwable {
		System.out.println("StartTime=[" + System.currentTimeMillis() + "]");
		Object returnObj = null;
		//下面三种调用方式都能执行
		returnObj = methodProxy.invokeSuper(object, objects);
		returnObj = method.invoke(delegate, objects);
		returnObj = methodProxy.invoke(delegate, objects);
        System.out.println("EndTime=[" + System.currentTimeMillis() + "]");
        return returnObj;
	}

}

methodProxy.invokeSuper调用原始类的方法,invoke执行子类的方法,如果使用methodProxy,invoke(object,objects)或者method.invke(object,objects) 会出现循环调用 直接程序报错。

然后定义一个CallbackFilter过滤,根据不同的方法名来 返回不同的Callback的索引:

package com.zhl.practice.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.CallbackFilter;
public class ProxyFilter implements CallbackFilter{
	@Override
	public int accept(Method method) {
		if("sayHello".equals(method.getName())){
			return 0 ;
		}
		return 1;
	}
}

调用方式如下:

package com.zhl.practice.cglib.test;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
import org.junit.Test;
import com.zhl.mytest.RealStudent;
import com.zhl.practice.cglib.AnotherRealStudentProxy;
import com.zhl.practice.cglib.ProxyFilter;
import com.zhl.practice.cglib.RealStudentProxy;
public class CglibTest {
	@Test
	public void testCglib(){
		RealStudentProxy studentProxy = new RealStudentProxy();
		AnotherRealStudentProxy anotherProxy = new AnotherRealStudentProxy();
		anotherProxy.delegate = new RealStudent();
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(RealStudent.class);
		enhancer.setCallbacks(new Callback[]{studentProxy,anotherProxy, NoOp.INSTANCE});
		enhancer.setCallbackFilter(new ProxyFilter());
		RealStudent reStudent = (RealStudent) enhancer.create();
		reStudent.sayHello();
		reStudent.sayHelloTwo();
	}
}

设置Callbacks 传递 数组 这里传递不同的 代理类实例,NoOp.INSTANCE代表空代理类,

ProxyFilter根据不同的方法 返回不同的索引,这里索引 对应Callbacks中,因此在调用sayHello会使用RealStudentProxy而调用其他方法(如sayHelloTwo)会使用AnotherRealStudentProxy来进行代理。

这样设置在构造函数中调用对应方法也会被拦截,如下在RealStudent类 里面添加一个构造函数

public RealStudent(){

       sayHello();

}

执行上面 的测试代码 发现 sayHello 被拦截了 两次,也就是构造函数中的 sayHello方法也被拦截处理了,如果不想让构造函数中调用的方法被拦截,那么可以使用:enhancer.setInterceptDuringConstruction(false);这里默认是ture,改为false就能让构造函数中调用方法时不被拦截。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值