代理:无论哪种代理,都存在代理对象和目标对象两个模型,所以目标对象就是我们要生成的代理对象代理的那个对象。
一、包装的动态代理
接口:
public interface Animal {
void eat(String food);
String type();
}
public interface Primate {
void think();
}
实现类:
public class Monkey implements Animal,Primate{
public void eat(String food) {
System.out.println("the food is "+food+" !");
}
public String type() {
String type="哺乳动物";
System.out.println(type);
return type;
}
public void think() {
System.out.println("思考");
}
}
包装类的代理:
public class AnimalWrapper implements Animal{
private Animal animal;
public AnimalWrapper(Animal animal){
this.animal=animal;
}
public void eat(String food) {
System.out.println("+++Wrapped Before!+++");
animal.eat(food);
System.out.println("+++Wrapped After!+++");
}
public String type() {
System.out.println("+++Wrapped Before!+++");
String type=animal.type();
System.out.println("+++Wrapped After!+++");
return type;
}
}
可以看到,AnimalWrapper完成了对Animal所有子类的代理,在代理方法中,可以加入一些自己的额外逻辑,Spring的前置、后置、环绕方法通知,都可以通过这种方式有限的模拟出来。
缺点就是,当Animal接口新增了新的方法,包装类也必须新增方法,而且一个包装类实际上只能对应一个接口。
二、继承的代理
public class ExtendProxyTest extends Monkey{
public static void main(String args[]){
}
public void eat(String food){
System.out.println("+++warpped brfore+++");
super.eat(food);
System.out.println("+++warpped after+++");
}
public String type(){
System.out.println("+++warpped brfore+++");
String type=super.type();
System.out.println("+++warpped after+++");
return type;
}
}
这种方式最简单,不过不能实现对Animal所有子类的代理,与包装的模式相比,大大缩小了代理范围。
三、动态代理
/**
* 基于reflect.Proxy、reflect.InvocationHandler两个类来完成的,使用java反射机制
*1.
Onbject proxy=Proxy.newProxyInstance(定义代理对象的类加载器,
要代理的目标对象的归属接口数组, 回调接口InvacationHandler);
*
*2. Onbject proxy=Proxy.newProxyInstance(定义代理对象的类加载器,
要代理的目标对象的归属接口数组);
proxy=proxClass.getConstructor(
new Class[]{InvocationHandler.class}).
newInstance(new 回调接口InvocationHandler);
缺点:不能对类进行代理,只能对接口进行代理。因为生成的代理类$Proxy这个类继承了Proxy,Java的
继承不允许出现多个父类*
*/
public class DynamicProxy {
public static void main(String args[]) throws {
/*
* 使用第一种方法来创建代理对象
*/
Object proxy=Proxy.newProxyInstance(
Monkey.class.getClassLoader(),
Monkey.class.getInterfaces(),
new AnimalInvocationHandler(new Monkey()));
/*
* 代理对象调用的时候,会调用回调的invoke方法,Method.args是调用invoke的时候传入的
*/
Animal animal=(Animal)proxy;
animal.eat("橡胶");
animal.type();
* 第2中方式 */
Class<?> proxClass=Proxy.getProxyClass(
Monkey.class.getClassLoader(),
Monkey.class.getInterfaces());
proxy=proxClass.getConstructor(new
Class[]{InvocationHandler.class}).
newInstance(new AnimalInvocationHandler(
new Monkey()));
animal=(Animal)proxy;
animal.eat("香蕉");
animal.type();
Primate pre=(Primate)proxy;
pre.think();
}}
回调类:
/**
* 回调类。
*
* InvocationHandler接口只有一个invoke需要实现,这个方法会在目标对象的方法调用的时候被激活
* 你可以在这里控制目标对象的方法的调用,在调用前后插入一些其他从操作
* 比如:鉴权、日志,事务管理等。
*
* 后两个参数,一个是调用的方法的Method对象,另一个是方法的参数,第一个参数,,就是我们使用Proxy
* 的静态方法创建的动态代理的对象,也就是$Proxy实例,由于$Proxy在JDK中不是静态存在的,所以不可以把
* Object proxy强制转换成$Proxy类型,因为你根本就无法从Classpath中道途$Proxy。那么我们可以把proxy
* 转换为目标接口对象吗?可以。因为$Proxy是实现了目标对象的,但是这样的实际意义不大,因为转换为
* 目标对象的接口之后,你调用接口中的任何方法,都会导致invoke的调用陷入死循环而导致栈溢出。
* 这是因为目标对象的大部分方法都被代理了,在invoke通过代理对象转换之后的接口,调用目标对象的方法
* 依然走的是代理对象
*
* 所以第一个参数,一般情况下都用不到,除非想获得代理对象$Proxy的类描述信息,它的getClass方法不会陷入死循环.
* 不过还是要注意,调用$proxy的从object上继承的方法,比如hashCode等也会导致陷入死循环,因为getClass()
* 方法是final的,不可以被覆盖,所以也就不会被Proxy代理。当然,因为Proxy代理的是Monkey接口,不是Monkey本身,
* 所以即使Monkey在实现Animal、Primate接口的时候,把方法都变为final,也不会影响到proxy的动态代理。 *
* *
*/
public class AnimalInvocationHandler implements InvocationHandler{
private Object obj;
public AnimalInvocationHandler(Animal animal){
this.obj=animal;
}
/**
* proxy是实际的代理对象
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("invoke method hefore");
Object returnObj=method.invoke(obj, args);
System.out.println("invoke method hefore");
return returnObj;
}
}
CGLIB
该类没有实现接口,并且有一个final方法
public class Monkey{
public void eat(String food) {
System.out.println("the food is "+food+" !");
}
public final String type() {
String type="哺乳动物";
System.out.println(type);
return type;
}
public void think() {
System.out.println("思考");
}
}
/**
* CGLIB进行动态代理的编程于Proxy没有多少不同,Enhancer是CGLIB的入口,通过它
* 创建代理对象,同时为代理对象分配一个Callbacl回调接口,用于执行回调。
*
* 常用的是MethodInterceptor,他继承Callback,用于执行方法拦截。另外还有一些内置的回调处理:
* 1.FixedValue,为提高性能,FixedValue回调对枪支某一特别方法返回固定值是有用的
* 2.NoOp,该回调把方法调用直接委派到这个方法在父类中的实现,相当于不处理
* 3.LazyLoader,当时机的对象需要延迟加载时,可以使用LazyLoader回调。一旦实际对象被装载,
* 它将被每一个调用代理对象的方法使用
*
* CGLIB被Hibernate、Spring等很多开源框架内部使用,用于完成对类的动态代理,Spring中的很多XML配置属性的proxy-target-class
* 默认都为false,含义就是默认不启用对目标类的动态代理,而是对接口动态代理。某些情况下,如果相对Struts2的Action或者Spring
* MVC的Controller进行动态代理,会发现默认Spring会报告找不到$Proxy的XXX方法,这是因为一般我们都不会给控制层写一个接口,
* 而是直接在实现类中写请求方法,这样JDK自带的Proxy是找不到这些方法的,应为他们不在借口中,此时就要设置proxy-target-class="true"
* 引入CGLIB/ASM等库。
*/
public class CglibMethodInterceptor implements MethodInterceptor{
/**
* 1.obj 代理对象
* 2.被调用的方法
* 3.方法的参数
* 4.CGLIB提供的方法代理对象
*
* 一般使用最后一个参数,而不是第2个,java的反射,因为CGLIB试用ASM的
* 字节码操作,代理对象的执行效率比反射机制更高.而且,试用method会造成死循环
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("=============================");
Object o=proxy.invokeSuper(obj, args);
//Object o1=method.invoke(obj, args);
System.out.println("++++++++++++++++++++++++++++++");
return o;
}
public static void main(String args[])
{
/**
* create方法用来创建代理对象
*/
Monkey mon=(Monkey)Enhancer.create(Monkey.class,
new CglibMethodInterceptor());
mon.think();
/*
* 发现type方法没有被代理,没有输出装饰的信息
* 这是因为CGLIB代理的原理是试用ASM动态生成目标对象的子类,final方法不能
* 被子类覆盖,所以也就不能被动态代理,这也是CGLIB的一个缺点
*/
mon.type();
mon.eat("xiangjiao");
System.out.println("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
/*
* 很多情况下,我们不是使用静态方法,而不是Enhancer的实例去完成动态代理对象的创建
* 因为试用实例,可以获得更多的功能,比如是否使用缓存,生成策略等等.
*
* ,默认情况,子类会继承父类无参的构造方法进行实例化,如果想
* 调用其他构造器,那么用.create(argumentTypes, arguments),参数分别是构造方法的
* 参数类型、传递参数
*/
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(Monkey.class);
/*
* 可以使用setCallbacks(Callback[] callbacks)方法为代理对象设置一组回调器
* 可以配合CallbackFilter为不同方法使用不同的回调器。CallbackFilter的accept方法
* 返回的是回调器的索引值
*/
enhancer.setCallbacks(new Callback[]{new CglibMethodInterceptor(),NoOp.INSTANCE});
enhancer.setCallbackFilter(new CallbackFilter() {
public int accept(Method method) {
// 方法think使用回调数组中的第二个回调器
if(method.getName().equals("think"))
return 1;
else return 0;
}
});
Monkey monk=(Monkey)enhancer.create();
monk.think();
monk.type();
monk.eat("xiangjiao");
}
}
联系客服