05- 代理模式
2. 什么是代理模式?
:通过代理来 控制对对象的访问。
比如:一个明星要开演唱会,明星本人只需要在舞台上唱歌,演唱会之前准备工作和演唱会之后的收尾工作都是交由明星助理做。这就是代理模式。
真实角色:它只关注真正的业务逻辑,比如歌星唱歌。
代理角色:在实现真正的业务逻辑前后可以附加一些操作,比如谈合同,布置场地,收钱等等。
2-1. 静态代理模式【静态代理是我们自己创建一个代理类】
**
* 静态代理类:
* 真实角色 & 代理角色 & 客户端
**/
//StaticProxy -- >代理角色
public class StaticProxy {
private Singer singer;
public StaticProxy(Singer singer) {
this.singer = singer;
}
public void sing(){
System.out.println("准备唱歌的场地等事宜 ~");
singer.Sing();
System.out.println("场地收尾工作 ~");
}
}
// --->客户端
class Client{
public static void main(String[] args) {
StaticProxy staticProxy = new StaticProxy(new Singer());
staticProxy.sing();
}
}
//歌唱的接口
interface Sing {
public void Sing();
}
// 歌手实现唱歌的接口 ---> 真实角色
class Singer implements Sing{
@Override
public void Sing() {
System.out.println("歌星小凯在唱歌 ~~");
}
}
2-2. 动态代理【动态代理是程序自动帮我们生成一个代理,我们就不用管了,使用的更广泛】
动态代理一般有两种方式:JDK 动态代理和 CGLIB 动态代理。
A: JDK 动态代理【基于接口的代理】
既然动态代理不需要我们去创建代理类,那我们只需要编写一个动态处理器就可以了。真正的代理对象由 JDK 在运行时为我们动态的来创建。
这里说一下 Proxy.newProxyInstance() 方法,该方法接收三个参数:
第一个参数指定当前目标对象使用的类加载器,获取加载器的方法是固定的;
第二个参数指定目标对象实现的接口的类型;
第三个参数指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法。
**
* 动态代理之一:
* 基于Jdk的动态代理,其底层实现是依赖于反射机制实现的代理
**/
//-- > 代理角色
public class JdkProxy implements InvocationHandler {
private Object object;
public JdkProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("唱歌之前的准备工作~");
Object invoke = method.invoke(object, args);
System.out.println("唱歌之后的收尾工作~~");
return invoke;
}
}
//---->客户端
class Clients{
public static void main(String[] args) {
Singinger singinger = new Singinger();
JdkProxy jdkProxy = new JdkProxy(singinger);
Object obj = Proxy.newProxyInstance(singinger.getClass().getClassLoader(), singinger.getClass().getInterfaces(), jdkProxy);
//不能用转换为接口的实现类, 只能转换为接口
Singing s = (Singing)obj;
s.Sing();
}
}
//--真实角色
class Singinger implements Singing{
@Override
public void Sing() {
System.out.println("歌星小凯在唱歌 ~~");
}
}
interface Singing{
void Sing();
}
B: CGLIB 动态代理【基于子类的代理】
其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术,拦截所有父类方法的调用,顺势织入横切逻辑(所以不能对final修饰的类进行代理)
package com.example.mayi.proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
使用 CGLIB 需要实现 MethodInterceptor 接口,并重写intercept 方法,
在该方法中对原始要执行的方法前后做增强处理。
该类的代理对象可以使用代码中的字节码增强器来获取。
**
*基于 cglib的动态代理 ,其底层是借助asm框架来实现代理
*依赖asm cglib 包
**/
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy methodProxy) throws Throwable {
System.out.println("饭做好可以吃饭了~~");
Object invokeSuper = methodProxy.invokeSuper(obj, arr);
System.out.println("饭吃完了,剩下一些没洗的碗~~");
return invokeSuper;
}
}
//-->client
class CLien{
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
//cglib代理使用的是asm框架
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Eater.class);
enhancer.setCallback(cglibProxy);
Object obj = enhancer.create();
Eat eat = (Eat)obj;
eat.Eat();
}
}
//真实的角色
class Eater implements Eat{
@Override
public void Eat() {
System.out.println("小凯在吃饭");
}
}
interface Eat{
void Eat();
}
基于接口的jdk动态代理和 基于子类的cglib动态代理的优缺点:
动态代理 :
(1)jdk动态代理 底层是基于java反射机制来实现的。所以jdk动态代理 在生产代理类的时候比较高效,要求必须是基于同一的接口
(2)cglib动态代理底层是基于asm框架实现的。所以cglib动态代理在相关过程执行的时候效率比较高(原因是因为可以通过将asm生产的类进行缓存,这样解决了asm在生产类过程中低效的问题),要求必须是基于一个类。
【asm其实就是java字节码控制】
(1)若目标对象实现了若干接口,
spring使用JDK的java.lang.reflect.Proxy类代理。
优点:大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度
缺点:JDK 实现动态代理需要通过接口定义业务方法由实现类执行,需为每一个目标类创建接口
(2)若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类
优点:
因为代理类与目标类是继承关系,所以不需要有接口的存在;
CGLIB 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,所 花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 更为合适一些。
缺点:
因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好;
对于final修饰的方法无法进行代理。
3. Spring AOP 采用哪种代理?
JDK 动态代理和 CGLIB 动态代理均是实现 Spring AOP 的基础。
Spring AOP 中的代理使用逻辑:
如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;如果目标对象没有实现了接口,则采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 动态代理之间转换。
说出Spring的通知类型有哪些?
写出创建代理对象需指定的三要素是什么?
(1)设定目标对象 (2)设定代理接口 (3)设定拦截器的名字
针对于这一块内容,我们看一下 Spring 5 中对应的源码是怎么说的。
//@Overridepublic
public AopProxy createAopProxy(AdvisedSupport config)throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
thrownew AopConfigException
("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation.");
}
// 判断目标类是否是接口或者目标类是否Proxy类型,若是则使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
returnnew JdkDynamicAopProxy (config);
}
// 配置了使用CGLIB进行动态代理或者目标类没有接口,那么使用CGLIB的方式创建代理对象
returnnew ObjenesisCglibAopProxy (config);
} else {
// 上面的三个方法没有一个为true,那使用JDK的提供的代理方式生成代理对象
returnnew JdkDynamicAopProxy (config);
}
}//if的大括号
//其他方法略……
}//类的大括号
从上述源码片段可以看出,是否使用 CGLIB 是在代码中进行判断的,判断条件是:
config.isOptimize()、config.isProxyTargetClass()
和 hasNoUserSuppliedProxyInterfaces(config)。
其中,config.isOptimize() 与 config.isProxyTargetClass()默认返回都是 false,这种情况下判断结果就由hasNoUserSuppliedProxyInterfaces(config)的结果决定了。
简单来说,hasNoUserSuppliedProxyInterfaces(config) 就是在判断代理的对象是否有实现接口,有实现接口的话直接走 JDK 分支,即使用 JDK 的动态代理。