您的位置:首页 > 编程语言 > Java开发

JDK设计模式之—动态代理

2018-06-17 17:59 477 查看

代理模式的特点

代理模式是常用的java设计模式,它的特征是代理类与委托类有同样的接口。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类。

代理类的对象并不是真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

按照代理类的创建时期,代理类可分为两种

  静态代理类:由程序员创建源代码,在对其编译。在程序运行之前,代理类的.class文件就已经存在了。

  动态代理类:在程序运行时,通过反射机制创建而成。

静态代理

1.首先我们写一个被代理类

package javaee.net.cn.proxy;
/**
* 需要动态代理的接口
*/
public interface Subject{
public void save();
}

2.在写一个实现类(实际被代理的对象)

package javaee.net.cn.proxy;
/**
* 实际对象
*/
public class RealSubject implements Subject{
public void save(){
System.out.println("insert into ......");
}

}

3 手动编写代理类

public class StaticProxy implements Subject{
private Subject subject;
public StaticProxy(Subject subject){
this.subject=subject;
}
@Override
public String save() {
System.out.println("trancation start");
String result = subject.save();
System.out.println("trancation commit");
return result;
}
}

动态代理

与静态代理对照的是动态代理,动态代理的字节码在程序运行时由java反射机制动态生成,无需程序员手工编写它的源代码。

动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为java反射机制可以生成任意类型的动态代理类

1和2与静态代理的代码不变

Proxy提供了创建动态代理类及其实例的静态方法。

,%20java.lang.reflect.InvocationHandler)]newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
 
          返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序

参数:

loader
 - 定义代理类的类加载器

    interfaces
 - 代理类要实现的接口列表

     

h
 - 指派方法调用的调用处理程序

返回:一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

InvocationHandler 接口为方法调用接口,它声明了负责调用任意一个方法的invoke()方法:

)]invoke(Object proxy, Method method, Object[] args)
 
          在代理实例上处理方法调用并返回结果。

参数:

proxy
 - 在其上调用方法的代理实例

   method
 - 对应于在代理实例上调用的接口方法的 
Method
 实例。 
Method
 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。

    args
 - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 
null
。基本类型的参数被包装在适当基本包装器类(如
java.lang.Integer
 或 
java.lang.Boolean
)的实例中。

返回:从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为 

null
 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 
NullPointerException
。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 
ClassCastException

3.实现InvocationHandler接口 用来创建一个InvocationHandler对象。

package javaee.net.cn.proxy;

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

/**
* 调用处理器实现类
* 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
*/
public class LogInterceptor implements InvocationHandler{
/**
* 这个就是我们要代理的真实对象
*/
private Object target;
/**
* 构造方法,给我们要代理的真实对象赋初值
* @param subject
*/
public LogInterceptor(Object target){
this.target = target;
}

/**
* 该方法负责集中处理动态代理类上的所有方法调用。
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
* @param proxy  代理类实例
* @param method 被调用的方法对象
* @param args   调用参数
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("trancation start");

//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object returnValue = method.invoke(target, args);

//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("trancation commit");
return returnValue;
}
}

4.Proxy,%20java.lang.reflect.InvocationHandler)]newProxyInstance方法创建代理类的实列(把上面Invocationhandler类的实列当做参数传过去)

  Proxy
 提供用于创建动态代理类和实例的静态方法

package javaee.net.cn.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
*  动态代理演示
*  通过分析代码可以看出Java 动态代理,具体有如下四步骤:
通过实现 InvocationHandler 接口创建自己的调用处理器;
通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
*/
public class Test{
public static void main(String[] args) {
//代理的真实对象
Subject realSubject = new RealSubject();
InvocationHandler handler = new LogInterceptor(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Class<?>[] interfaces = realSubject.getClass().getInterfaces();
/**
* 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
*/
Subject subjectProxy = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
subjectProxy.save();
}
}

调用顺序:当我们调用subjectProxy 的save()方法 会进入 InvocationHandler实现类的invoke()方法,invoke()方法里面会执行realSubject的save()方法

下面是方法运行的结果

trancation start
insert into ......
trancation commit

像是Spring的事物吧。Spring AOP管理的事物,原理也是动态代理

这是对反射和classLoader的一些补充解释

动态代理的应用:Spring的AOP

对于JDK而言,它是要求被代理的目标对象必须拥有接口,而对于CGLIB则不做要求。默认情况下,Spring会安装一条这样的规则处理

当你需要使用AOP的类拥有接口时,它会以JDK动态代理运行,否则以CGLIB运行。

思考:Spring中如何强制使用CGLIB实现AOP?
 (1)添加CGLIB库
 (2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

@Transactional自调用失效的问题

我们在一个类userService的接口A,调用userService的另一个接口B。虽然接口A和接口B都加上了@Transactional注解。但是对于B接口而言 事物不生效(出现错误不会回滚)。

原因:Spring数据库事物的约定实现的原理是AOP,而AOP的原理是动态代理,在自调用的过程中是类自身的调用,而不是代理对象去调用,那么久不会产生AOP,

这样Spring就不能把你的代码织入到约定的流程中。

解决方法: 想办法把类调用其内部的方法编程代理类之间的调用,那就是 从IOC容器中再去获得一次类userService,此时获取的对象是Spring IOC容器中的代理对象

最后用新获取的对象执行接口B,此时接口A和接口B的@Transaction都会生效。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: