代理模式概述
思维导图
- 什么是代理模式:无法访问目标对象,通过代理对象进行访问,而且增强式的访问。适合进行业务的扩展。
代理模式是指,为其他对象提供一种代理,以控制对这个(目标)对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户和目标对象之间起到中介的作用。 使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。
例如:A类想要访问B类,但他不能直接访问,必须通过Proxy代理来来访问
功能:
- 增强功能
- 限制目标对象的访问
分类:
- 静态代理
- 动态代理(JDK动态代理、CGLIB动态代理)
静态代理
特点:
优点: 静态代理要求目标对象和代理对象实现同一个业务接口。代理对象中的核心功能是由目标对象来完成,代理对象负责增强功能。 缺点: 当业务发生变化时,要进行大量代码的改动,实现复杂; 能够灵活的进行目标对象的切换,却无法进行功能的灵活处理; 代理类是以.java的文件形式存在,在调用前就已存在,所以比较死板;
实现步骤:
定义业务接口
public interface Service {
void sing();
}
定义目标对象类实现接口
//实现类1
public class ChangChang implements Service{
@Override
public void sing() {
System.out.println("我是邓长长....我正在唱歌");
}
}
//实现类2
public class DuanDuan implements Service {
@Override
public void sing() {
System.out.println("我是邓短短....我正在唱歌");
}
}
定义代理类实现接口
public class Agent implements Service {
@Override
public void sing() {
System.out.println("正在预约时间....");
System.out.println("正在预约地点....");
//根据目标对象来完成业务
Service chang = new ChangChang();
chang.sing();
System.out.println("正在结算....");
}
}
代理功能改造(采用面向接口编程)
面向接口编程的要点:
A.类中的成员变量设计为接口 B.方法的参数设计为接口 C.方法的返回值设计为接口 D.调用时接口指向实现类
public class Agent implements Service {
//类中的成员变量设计为接口
private Service target;//需要代理的接口
/**
* 传入目标对象,方法的参数设计为接口
* 通过构造方法,动态定义接口实现类,根据参数来代理指定对象
* @param target
*/
public Agent(Service target){
this.target = target;
}
@Override
public void sing() {
System.out.println("正在预约时间....");
System.out.println("正在预约地点....");
//面向接口编程:调用时,接口指向实现类
target.sing();
System.out.println("正在结算....");
}
@Override
public String show(int age) {
return null;
}
}
静态代理的缺陷
- 代理复杂,难于管理
代理类和目标类实现了相同的接口,每个代理都需要实现目标类的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 代理类依赖目标类,代理类过多
代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多
动态代理
动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的。动态代理不需要定义代理类的.java 源文件。动态代理其实就是 jdk 运行期间,动态创建 class 字节码并加载到 JVM。动态代理的实现方式常用的有两种:使用 JDK 动态代理和 CGLIB 动态代理。
JDK 动态代理
JDK动态代理是基于 Java 的反射机制实现的。使用JDK中接口和类实现代理对象的动态创建。 代理对象在程序运行的过程中动态在内存构建;可以灵活的进行业务功能的切换。
特点:
- 目标对象必须实现业务接口
- 代理对象不必实现业务接口
- JDK动态代理的对象在程序运行前不存在,在程序运行时动态的在内存中构建
- JDK动态代理灵活的进行业务功能的切换
- 非接口中的方法不能被代理
Proxy 类
通过JDK的java.lang.reflect.Proxy类实现动态代理,会使用其静态方法newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。
public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,
InvocationHandler handler)
方法参数介绍:
loader:目标类的类加载器,通过目标对象的反射可获取 interfaces:目标类实现的接口数组,通过目标对象的反射可获取 handler:调用处理器。
Method 类
java.lang.reflect.Method
invoke()方法的第二个参数为 Method 类对象,该类有一个方法也叫 invoke(),可以调用目标方法。这两个 invoke()方法,虽然同名,但无关。
用来进行目标对象的方法的反射调用.
方法参数介绍:
public Object invoke ( Object obj, Object… args) obj:表示目标对象 args:表示目标方法参数,就是其上一层invoke 方法的第三个参数
该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定。在代码中,一般的写法为
method.invoke(target, args);
其中,method 为上一层 invoke 方法的第二个参数。这样,即可调用了目标类的目标方法。
InvocationHandler接口
java.lang.reflect.InvocationHandler;
InvocationHandler 接口叫做调用处理器,负责完成调用目标方法,并增强功能。通过代理对象执行目标接口中的方法,会把方法的调用分派给调用处理器(InvocationHandler)的实现类,执行实现类中的 invoke()方法,我们需要把功能代理写在 invoke()方法中。此接口中只有一个方法。
public Object invoke ( Object proxy, Method method, Object[] args)
方法参数介绍:
proxy:代表生成的代理对象 method:代表目标方法 args:代表目标方法的参数 这三个参数都是 jdk 运行时赋值的,无需程序员给出。
实现步骤
注:JDK动态代理中,代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用JDK动态代理
定义业务接口
public interface Service {
void sing();
String show(int age);
}
定义业务实现类
public class ChangChang implements Service{
@Override
public void sing() {
System.out.println("我是邓长长....我正在唱歌");
}
@Override
public String show(int age) {
return "我今年"+age+"岁";
}
}
定义代理类工厂
public class ProxyFactory {
//类中的成员变量设计为接口,目标对象
Service target;
//传入目标对象
public ProxyFactory(Service target){
this.target = target;
}
//返回动态代理对象
public Object getAgent(){
return Proxy.newProxyInstance(
//ClassLoader loader,类加载器,完成目标对象的加载
target.getClass().getClassLoader(),
//Class<?>[] interfaces,目标对象实现的所有接口
target.getClass().getInterfaces(),
//InvocationHandler h 实现代理功能的接口 ,采用匿名内部实现
new InvocationHandler() {
/**
*
* @param proxy 生成的代理对象
* @param method 目标方法
* @param args 目标方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理功能
System.out.println("预订时间........");
//代理功能
System.out.println("预订场地........");
//主业务功能实现
Object obj = method.invoke(target, args);
//代理功能
System.out.println("结算费用........");
return obj;//目标方法的返回值
}
}
);
}
}
测试 ```java //测试方法 @Test public void getAgent() {
//创建需要被代理的对象,目标对象
ChangChang changChang = new ChangChang();
//创建动态代理工厂类
ProxyFactory factory = new ProxyFactory(changChang);
Service agent = (Service) factory.getAgent();
agent.sing();
}
//运行结构 / 预订时间…….. 预订场地…….. 我是邓长长….我正在唱歌 结算费用……../ ``` 注意:
Service agent = (Service) factory.getAgent(); 在强转时只能强转为接口,不能是实现类 因为: System.out.println(agent.getClass());//class com.sun.proxy.$Proxy4
System.out.println(new ChangChang());//com.ityg.service.impl.ChangChang@15327b79