文章目录
代理模式的定义
给目标对象提供一个代理对象,并由代理对象控制目标对象的引用
代理模式的作用
- 通过代理对象对目标对象进行业务增强
- 间接访问目标对象,防止直接访问目标对象带来的复杂性【这个作用目前没有理解】
代理模式三要素
- 抽象接口,定义功能行为
- 真实对象,实现了抽象对象
- 代理对象,实现了抽象对象,并包含了真实对象
静态代理和动态代理,代理对象都必须持有目标对象【被代理对象,也称为目标对象】
静态代理时,被代理对象和代理对象都必须实现接口,且必须是同一个接口。
动态代理时,被代理对象必须实现接口,因为代理对象是通过该接口的反射机制创建的,即
代理对象=Proxy.newProxyInstance(被代理对象.getClass().getClassLoader(),被代理对象.getClass().getInterfaces(), this);
静态代理
Demo实现的功能:代理苹果手机专卖店,在网上买手机,增强了打广告的功能
//抽象对象
public interface PhoneShop {
void sellPhone(String phoneName);
}
//被代理的目标对象
public class ApplePhoneShop implements PhoneShop {
@Override
public void sellPhone(String phoneName) {
System.out.println("苹果手机专门店正在卖:" + phoneName);
}
}
//用于增强的代理对象
public class OnlineShop implements PhoneShop {
private PhoneShop phoneShop;
public OnlineShop(PhoneShop phoneShop) {
this.phoneShop= phoneShop;
}
@Override
public void sellPhone(String phoneName) {
System.out.println("手机广告推广");
phoneShop.sellPhone(phoneName);
System.out.println("卖完了分钱");
}
}
public class StaticProxyDemo {
public static void main(String[] args) {
OnlineShop onlineShop = new OnlineShop(new ApplePhoneShop());
onlineShop.sellPhone("苹果12 pro");
}
}
运行结果:
静态代理的缺点:
静态代理的扩展性不好,如果想在接口中增加方法,必须修改代理对象中的代码,明显违背了开闭原则。为了解决静态代理的缺点,由此出现了动态代理。
开闭原则【对扩展开放,对修改关闭】:只额外增加代码模块,不修改原有功能代码
动态代理
Spring动态代理的两种实现方式
java动态代理:通过java.lang.reflect.Proxy类的newProxyInstance方法创建代理对象【反射机制】,再通过代理对象调用被代理对象的方法,实际是通过invoke方法调用
cglib动态代理【Code Generator Library】:修改内存中目标对象类的字节码文件,生成其子类。
为什么使用CGLIB动态代理
JDK动态代理要求代理对象类必须实现接口,不能代理没有实现接口的普通类,因为JDK动态代理创建代理对象时,是通过接口反射机制来实现的:Proxy.newProxyInstance(被代理对象.getClass().getClassLoader(),被代理对象.getClass().getInterfaces(), this);
什么是 CGLIB
CGLIB(Code Generator Library)是一个代码生成库。其被广泛应用于AOP框架。GLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。
JDK动态代理和CGLIB字节码生成的区别
(1)JDK动态代理要求代理对象类必须实现接口,不能代理没有实现接口的普通类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆写其中的方法。
CGlib动态代理不要求代理对象类必须实现接口,CGlib使用的是继承机制,代理类继承委托类。CGlib要求委托类不能是final的,否则会创建代理失败,因为final类是不允许继承的
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
JDK动态代理的实现
JDK动态代理的条件:
(1)被代理的对象必须要实现接口
(2)实现InvocationHandler接口
(3)使用Proxy.newProxyInstance产生代理对象
1.实现动态代理的两个核心:InvocationHandler接口、Proxy类
- InvocationHandler接口,提供了invoke方法,实现该方法主要作用:
代理对象调用被代理对象的方法通过invoke方法来实现 - Proxy类用来创建一个代理对象,主要通过它的静态方法newProxyInstance方法
public interface Girl {
void shopping(String goodsName);
void movie(String filmName);
}
public class WangMeiNv implements Girl {
@Override
public void shopping(String goodsName) {
System.out.println("今天王美女买了:" + goodsName);
}
@Override
public void movie(String filmName) {
System.out.println("今天王美女看了电影:" + filmName);
}
}
public class WangMeiNvPoxy implements InvocationHandler {
private Girl girl;
public WangMeiNvPoxy() {
}
public WangMeiNvPoxy(Girl girl) {
this.girl = girl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法之前做一些事情!");
Object ret = method.invoke(girl, args);
System.out.println("调用方法之后做一些事情!");
return ret;
}
//创建该代理对象
public Object getNewInstance(){
return Proxy.newProxyInstance(girl.getClass().getClassLoader(), girl.getClass().getInterfaces(), this);
}
}
代理对象的使用
@Test
public void test1(){
Girl wangMeiNv = new WangMeiNv();
WangMeiNvPoxy wangMeiNvPoxy = new WangMeiNvPoxy(wangMeiNv);
Girl newInstance = (Girl) wangMeiNvPoxy.getNewInstance();
newInstance.shopping("超短裙");
System.out.println("--------------------");
newInstance.movie("坦坦尼克号");
}
调用方法之前做一些事情!
今天王美女买了:超短裙
调用方法之后做一些事情!
--------------------------
调用方法之前做一些事情!
今天王美女看了电影:坦坦尼克号
调用方法之后做一些事情!
CGLIB动态代理的实现
参考文章:
https://www.jianshu.com/p/296684ba7fe6
https://blog.csdn.net/gyshun/article/details/81000997
AOP和动态代理的关系
- AOP面向切面编程是一种编程思想,是一种横向抽取代码的机制,补充了面向对象编程思想只能纵向父子集成的不足。
- 动态代理只是AOP这种编程思想的实现方式之一
- Spring的AOP是基于动态代理和反射机制实现的
动态代理失效的情况分析
public interface Girl {
void shopping(String goodsName);
void movie(String filmName);
}
public class WangMeiNv implements Girl {
@Override
public void shopping(String goodsName) {
System.out.println("==============这里此次代码分析的重点=================");
//这里此次代码分析的重点
this.movie("泰塔尼克号");
}
@Override
public void movie(String filmName) {
System.out.println("今天王美女看了电影:" + filmName);
}
}
public class WangMeiNvPoxy implements InvocationHandler {
private Girl girl;
public WangMeiNvPoxy() {
}
public WangMeiNvPoxy(Girl girl) {
this.girl = girl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法之前做一些事情!");
Object ret = method.invoke(girl, args);
System.out.println("调用方法之后做一些事情!");
return ret;
}
//创建该代理对象,这里this代表实现InvocationHandler接口的当前代理对象
public Object getNewInstance(){
return Proxy.newProxyInstance(girl.getClass().getClassLoader(), girl.getClass().getInterfaces(), this);
}
}
@Test
public void test1(){
Girl wangMeiNv = new WangMeiNv();
WangMeiNvPoxy wangMeiNvPoxy = new WangMeiNvPoxy(wangMeiNv);
Girl newInstance = (Girl) wangMeiNvPoxy.getNewInstance();
newInstance.shopping("超短裙");
System.out.println("--------------------");
//newInstance.movie("坦坦尼克号");
}
调用方法之前做一些事情!
今天王美女买了:超短裙
==============这里此次代码分析的重点=================
今天王美女看了电影:泰塔尼克号
调用方法之后做一些事情!
--------------------
为什么这次shopping方法中直接调用moive方法后,这里的结果和之前的不同呢?
原因:这里的this指带的WangMeiNv类的对象,而不是代理类实例。只有代理实例调用方法时,方法才会通过AOP实现业务增强处理
public class WangMeiNv implements Girl {
@Override
public void shopping(String goodsName) {
System.out.println("==============这里此次代码分析的重点=================");
//这里此次代码分析的重点
this.movie("泰塔尼克号");
}
@Override
public void movie(String filmName) {
System.out.println("今天王美女看了电影:" + filmName);
}
}
解决办法:
https://blog.csdn.net/Dongguabai/article/details/80788585