Java 代理模式
代理模式
什么是代理模式?(简单举例几个例子)
1、打王者段位一直打不上王者段位怎么办?请游戏代练。
2、过年回家自已抢不到高铁票怎么办?找黄牛帮我们抢票。
3、.......(生活中处处可见。)
由上可知,代理模式 就是:
给某一个对象提供一个代理对象,并由代理对象控制对 原对象的引用。
通俗滴讲就是我们常说的:中介。
有哪几种代理模式(它们的区别与优缺点)?
一般分为两种:
1、静态代理 (代理类与被代理类的关系在程序运行前就已经确定好了。)
2、动态代理 (代理类与被代理类的关系在程序运行时才确定。)
- jdk动态代理
- cglib动态代理
什么是静态代理?
在程序运行前就已经存在的代理类的字节码文件。
代理类和委托类的关系在运行前就确定了。
什么是动态代理?
在程序运行时,通过反射机制动态创建的。
静态代理 与 动态代理 优缺点
优点
实现在不改变目标对象源码情况下,对目标对象进行功能扩展。
静态代理缺点
假设项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不好维护,不能应对变化。
JDK动态代理缺点
被代理的类必须实现接口,未实现接口则没办法完成动态代理。
CGLIB动态代理缺点
被代理类必须不是 final 类。
代码实现代理
代码背景:某人(王五)吃苹果。
简单的代理(耦合性太强,采用继承的方式)不推荐使用
没有代理的情况下:
/**
* 王五
* @author oukele
*/
public class WangWu {
/**
* 吃苹果
*/
public void eatApple(){
System.out.println("王五吃苹果!");
}
}
运行结果:
public static void main(String[] args) {
WangWu wangWu = new WangWu();
wangWu.eatApple();
}
王五吃苹果!
有代理的情况下:
(张三将削好的苹果递给王五,然后王五吃苹果!)
/**
* 张三
* @author oukele
*/
public class ZhangSan extends WangWu {
@Override
public void eatApple() {
System.out.println("张三从众多苹果中挑了一个苹果。");
System.out.println("1. 然后拿去洗了洗...。");
System.out.println("2. 将苹果鲜红的外衣悄悄的脱掉...。");
System.out.println("N. N步操作在这里省略...。");
System.out.println("将处理好的苹果递给王五。");
// 王五吃苹果
super.eatApple();
}
}
运行结果:
public static void main(String[] args) {
ZhangSan zhangSan = new ZhangSan();
zhangSan.eatApple();
}
张三从众多苹果中挑了一个苹果。
- 然后拿去洗了洗...。
- 将苹果鲜红的外衣悄悄的脱掉...。
N. N步操作在这里省略...。
将处理好的苹果递给王五。
王五吃苹果!
上面的这些例子就是一个简单的代理行为。这个简单代理,耦合性太强了。作为演示就好了。
1、静态代理
代码背景:还是上面的例子,某人(王五)吃苹果。
第一步:创建 服务接口
/**
* 吃的服务行为接口
* @author oukele
*/
public interface Eat {
/**
* 吃的行为
*/
void eat();
}
第二步:实现服务接口
/**
* 王五(被代理类)
* @author oukele
*/
public class WangWu implements Eat {
@Override
public void eat() {
System.out.println("王五吃苹果!");
}
}
第三步:创建代理类(让张三来帮忙削皮)
/**
* 张三(代理类)
*
* @author oukele
*/
public class ZhangSan implements Eat {
/**
* 被代理类(王五)
*/
private Eat eat = null;
public ZhangSan(Eat eat){
this.eat = eat;
}
@Override
public void eat() {
System.out.println("0.-----张三的操作-----");
System.out.println("1.张三帮王五削好了苹果。");
// 王五吃苹果
eat.eat();
System.out.println("3.王五吃完了,张三顺便帮忙收拾好。");
}
}
第四步:进行测试
public static void main(String[] args) {
System.out.println("-------------------没有代理----------------------");
// 被代理类
WangWu wangWu = new WangWu();
wangWu.eat();
System.out.println("-------------------没有代理----------------------");
System.out.println();
System.out.println("-------------------有代理----------------------");
// 代理类
ZhangSan zhangSan = new ZhangSan(wangWu);
zhangSan.eat();
System.out.println("-------------------有代理----------------------");
}
运行结果:
-------------------没有代理----------------------
王五吃苹果!
-------------------没有代理-----------------------------------------有代理----------------------
0.-----张三的操作-----
1.张三帮王五削好了苹果。
王五吃苹果!
3.王五吃完了,张三顺便帮忙收拾好。
-------------------有代理----------------------
2、JDK动态代理
代码背景:还是上面的例子,某人(王五)吃苹果。
第一步:创建 服务接口
/**
* 吃的服务行为接口
* @author oukele
*/
public interface Eat {
/**
* 吃的行为
*/
void eat();
}
第二步:实现服务接口
/**
* 王五(被代理类)
* @author oukele
*/
public class WangWu implements Eat {
@Override
public void eat() {
System.out.println("王五吃苹果!");
}
}
第三步:创建代理类并实现 InvocationHandler 接口,这次就不叫 张三来帮忙了
/**
* 使用JDK内置的Proxy实现动态代理(代理类)
* @author oukele
*/
public class JdkProxy implements InvocationHandler {
/**
* 被代理类
*/
private Object object = null;
private JdkProxy(){};
public JdkProxy(Object object){
this.object = object;
}
/**
* 用户调用代理对象的什么方法,都是在调用处理器的invoke方法。【被拦截】
* @param proxy 被代理后的对象
* @param method 将要被执行的方法
* @param args 调用方法时需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("某某人....帮王五削好苹果。");
Object invoke = null;
try {
invoke = method.invoke(object, args);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("出现异常:" + e.getMessage());
}
System.out.println("某某人....帮忙收拾好现场。");
return invoke;
}
}
第四步:进行测试
public static void main(String[] args) {
Eat eat = (Eat) Proxy.newProxyInstance(
WangWu.class.getClassLoader(),
WangWu.class.getInterfaces(),
new JdkProxy(new WangWu())
);
eat.eat();
}
注意 Proxy.newProxyInstance() 方法接受三个参数
1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法是固定的。A
2. Class<?>[] interfaces: 指定目标对象实现的接口类型。 B
3. InvocationHandler: 指定动态处理器,执行目标对象的方法时,触发事件处理器的方法。C
大白话:
A: 生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】
B: 生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】
C: 生成的代理对象的方法里干什么事【实现InvocationHandler接口,我们想怎么增强原有对象的功能就随意怎么增强。】
运行结果:
某某人....帮王五削好苹果。
王五吃苹果!
某某人....帮忙收拾好现场。
3、CGLIB动态代理
其原理:通过字节码技术为一个类创建子类。
代码背景:还是上面的例子,某人(王五)吃苹果。
注意:需要 cglib 的依赖;
此处使用的版本为
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1、第一步:创建一个 王五 类
/**
* 王五
* @author oukele
*/
public class WangWu {
/**
* 吃苹果
*/
public void eatApple(){
System.out.println("王五吃苹果!");
}
}
2、第二步:创建 CGLIB 代理类 并实现 MethodInterceptor 接口
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Cglib 动态代理类
* @author oukele
*/
public class CglibProxy implements MethodInterceptor {
/**
*
* @param o 由CGLib动态生成的代理类实例
* @param method 上文中实体类所调用的被代理的方法引用
* @param objects 参数值列表
* @param methodProxy 生成的代理类对方法的代理引用
* @return 代理实例的方法调用返回的值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("某某人....帮王五削好苹果。");
Object invoke = null;
try {
invoke = method.invoke(o, objects);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("出现异常:" + e.getMessage());
}
System.out.println("某某人....帮忙收拾好现场。");
return invoke;
}
}
3、第三步:进行测试
public static void main(String[] args) {
// 增强器,动态代码生成器
Enhancer enhancer = new Enhancer();
// 设置 被代理类
enhancer.setSuperclass(WangWu.class);
// 放置 代理类
enhancer.setCallback(new CglibProxy());
// 动态生成字节码并返回代理对象
WangWu wangWu = (WangWu) enhancer.create();
wangWu.eat();
// 简写
WangWu wangWu1 = (WangWu)Enhancer.create(
WangWu.class,
new CglibProxy()
);
wangWu1.eat();
}
运行结果:
某某人....帮王五削好苹果。
王五吃苹果!
某某人....帮忙收拾好现场。
小结:
JDK 动态代理 和 CGLIB 字节码生成的区别?
JDK动态代理
只能对实现了接口的类生成代理
CGLIB动态代理
针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,
并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final.
JDK 代理 与 CGLIB 代理 中如何选择?
目标对象是否实现了接口
是
采用JDK的动态代理
否
采用CGLIB动态代理