代理模式
- 一、了解代理
- 二、代理讲解
- 1. 静态代理
- 静态代理违反了开闭原则:
- 2.JDK动态代理
- 2.1 这里需要讲解一下红框内的参数
- 2.2 代码
- 3. Cglib代理
- 3.1代码
- 4. 总结:
一、了解代理
代理(Proxy) 是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法
java中的代理模式:
定义:给目标对象提供一个代理对象,并且由代理对象控制对目标对象的引用
目的:
- ①:通过代理对象的方式间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性
- ②:通过代理业务对原有业务进行增强
java当中有三种方式来创建代理对象:静态代理,基于jdk(接口)的动态代理,基于CGLLIB(父类)的动态代理。
下面逐步讲解
二、代理讲解
1. 静态代理
细节:我们的真实对象必须实现我们的接口,同时代理对象也必须实现这一接口
接口类TargetInteface
接口
内部定义了三个方法
package com.step.aopdemo.staticproxy;
public interface TargetInteface {
void method1();
void method2();
int method3(Integer i);
}
接口实现目标类Target
package com.step.aopdemo.staticproxy;
import com.step.aopdemo.jdkproxy.TargetInteface;
public class Target implements TargetInteface {
@Override
public void method1() {
System.out.println("method1 running ...");
}
@Override
public void method2() {
System.out.println("method2 running ...");
}
@Override
public int method3(Integer i) {
System.out.println("method3 running ...");
return i;
}
}
接口实现代理类TargetProxy
package com.step.aopdemo.staticproxy;
public class TargetProxy implements TargetInteface {
@Override
public void method1() {
System.out.println("执行方法前...");
new Target().method1();
System.out.println("执行方法后...");
}
@Override
public void method2() {
System.out.println("执行方法前...");
new Target().method2();
System.out.println("执行方法后...");
}
@Override
public int method3(Integer i) {
System.out.println("执行方法前...");
int method3 = new Target().method3(i);
System.out.println("执行方法后...");
return method3;
}
}
测试类
package com.step.aopdemo.staticproxy;
public class TargetUser {
public static void main(String[] args) {
TargetInteface target = new TargetProxy();
target.method1();
System.out.println("-----------------------------");
target.method2();
System.out.println("-----------------------------");
System.out.println(target.method3(3));
}
}
执行结果
总结:
静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。
静态代理存在哪些问题?
不足:静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。
静态代理违反了开闭原则:
程序对外扩展开放,对修改关闭,换句话来说,当需求发生变化时,我们可以增加新模块来解决新需求,而不是通过该变原来的代码来解决我们的新需求
2.JDK动态代理
为解决静态代理对象必须实现接口的所有方法的问题,Java给出了动态代理,动态代理具有如下特点:
- Proxy对象不需要implements接口;
- Proxy对象的生成利用JDK的Api,在JVM内存中动态的构建Proxy对象。需要使用java.lang.reflect.Proxy类的
首先我们看一下java.lang.reflect.Proxy类的源码,先了解原理
源码703行开始讲解了 static Object newProxyInstance
方法
2.1 这里需要讲解一下红框内的参数
- 来看源码
ClassLoader loader
他是一个抽象类ClassLoader loader
:指定当前target对象使用类加载器,获取加载器的方法是固定的; - 看
Class<?>[] interfaces
源码介绍Class<?>[] interfaces
:target对象实现的接口的类型,使用泛型方式确认类型 - 看
InvocationHandler h
介绍——(interface)接口InvocationHandler h
:事件处理,执行target对象的方法时,会触发事件处理器的方法,会把当前执行target对象的方法作为参数传入。
2.2 代码
接口和静态代理接口TargetInteface
一毛一样,不再赘述
目标类和静态目标类Target
一致,同上
看点不一样的
package com.step.aopdemo.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* @author step
* @date 2021/8/14 15:53
*/
public class TargetProxy {
public static <T> Object getTarget(T t) {
return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。
//比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。
System.out.println("jdk执行方法前...");
Object invoke = method.invoke(t, args);
System.out.println("jdk执行方法后...");
return invoke;
}
});
}
}
单纯就这个书写的来说我又学到一个新的书写模式,这里做个小笔记用另外一种lambda 表达式传参
package com.step.aopdemo.jdkproxy;
import java.lang.reflect.Proxy;
public class TargetProxy {
public static <T> Object getTarget(T t) {
return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(),(proxy,method,args)->{
// proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。
//比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。
System.out.println("jdk执行方法前...");
Object invoke = method.invoke(t, args);
System.out.println("jdk执行方法后...");
return invoke;
});
}
}
使用了method.invoke(t, args);
方法,查看源码
上节我们讲过反射的原理java核心技术——反射,方便解释直接截图
上述两个方法二选一即可
测试类
package com.step.aopdemo.jdkproxy;
public class TargetUser {
public static void main(String[] args) {
TargetInteface target = (TargetInteface) TargetProxy.getTarget(new Target());
target.method1();
System.out.println("-----------------------------");
target.method2();
System.out.println("-----------------------------");
System.out.println(target.method3(3));
}
}
执行结果
3. Cglib代理
JDK动态代理要求target对象是一个接口的实现对象,假如target对象只是一个单独的对象,并没有实现任何接口,这时候就会用到Cglib代理(Code Generation Library),即通过构建一个子类对象,从而实现对target对象的代理,因此目标对象不能是final类(报错),且目标对象的方法不能是final或static(不执行代理功能)。
Cglib依赖的jar包
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
3.1代码
目标类Target
,(不用实现接口)
package com.step.aopdemo.cglibproxy;
/**
*
* @author zxj
* @date 2021/8/14 16:15
*/
public class Target {
public void method1() {
System.out.println("method1 running ...");
}
public void method2() {
System.out.println("method2 running ...");
}
public int method3(Integer i) {
System.out.println("method3 running ...");
return i;
}
}
代理类TargetProxy
package com.step.aopdemo.cglibproxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TargetProxy {
static <T> Object getProxy(T t) {
Enhancer en = new Enhancer(); //帮我们生成代理对象
en.setSuperclass(t.getClass());//设置要代理的目标类
en.setCallback(new MethodInterceptor() {//代理要做什么
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib执行方法前。。。");
//调用原有方法
Object invoke = methodProxy.invokeSuper(object, args);
// Object invoke = method.invoke(t, args);// 作用等同与上面。
System.out.println("Cglib执行方法后。。。");
return invoke;
}
});
return en.create();
}
}
测试类
package com.step.aopdemo.cglibproxy;
public class TargetUser {
public static void main(String[] args) {
Target target = (Target) TargetProxy.getProxy(new Target());
target.method1();
}
}
运行结果
4. 总结:
理解上述Java代理后,也就明白Spring AOP的代理实现模式,即加入Spring中的target是接口的实现时,就使用JDK动态代理,否是就使用Cglib代理。Spring也可以通过<aop:config proxy-target-class="true">
强制使用Cglib代理,使用Java字节码编辑类库ASM操作字节码来实现,直接以二进制形式动态地生成 stub 类或其他代理类,性能比JDK更强。