文章目录
1.什么是代理模式
为其他对象提供一种代理以控制对这个对象的访问。
2.为什么使用代理模式
- 中介隔离:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 开闭原则,增加功能代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
3.代理模式实现原理
代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色,Proxied)以及代理类角色(Proxy),如上图所示:
- 抽象主题角色:可以是接口,也可以是抽象类;
- 委托类角色:真实主题角色,业务逻辑的具体执行者;
- 代理类角色:内部含有对真实对象RealSubject的引用,负责对真实- 主题角色的调用,并在真实主题角色处理前后做预处理和后处理。
4.代理模式应用场景
SpringAop、日志收集、权限控制、过滤器、RPC远程调用
5.代理模式创建的方式
- 静态代理
- 动态代理
5.1 静态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。
所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
一句话,自己手写代理类就是静态代理。
5.1.1 基于接口实现方式
public interface OrderService {
void order();
}
public class OrderServiceImpl implements OrderService {
@Override
public void order() {
System.out.println("用户下单操作..");
}
}
public class OrderServiceProxy implements OrderService {
/**
* 代理对象
*/
private OrderService proxiedOrderService;
public OrderServiceProxy( OrderService orderService) {
this.proxiedOrderService=orderService;
}
@Override
public void order() {
System.out.println("日志收集开始..");
proxiedOrderService.order();
System.out.println("日志收集结束..");
}
}
public class ClientTest {
public static void main(String[] args) {
OrderService orderService = new OrderServiceProxy(new OrderServiceImpl());
orderService.order();
}
}
5.1.2 基于实现类方式
public class OrderServiceImpl{
public void order() {
System.out.println("用户下单操作..");
}
}
public class OrderServiceProxy extends OrderServiceImpl {
@Override
public void order() {
System.out.println("日志收集开始..");
super.order();
System.out.println("日志收集结束..");
}
}
public class ClientTest {
public static void main(String[] args) {
OrderService orderService = new OrderServiceProxy();
orderService.order();
}
}
5.2 动态代理
静态代理缺点是就是如果被代理类较多,那么代理独享就会有多个不便于维护。
动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成 。
5.2.1 JDK动态代理
JDK动态代理的一般步骤如下:
- 创建被代理的接口和类;
- 实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;
- 调用Proxy的静态方法,创建代理类并生成相应的代理对象;
public interface OrderService {
void order();
}
public class OrderServiceImpl implements OrderService {
@Override
public void order() {
System.out.println("修改数据库订单操作..");
}
}
public class JdkInvocationHandler implements InvocationHandler {
/**
* 目标代理对象
*/
public Object target;
public JdkInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>>日志收集开始>>>>");
// java的反射机制执行方法 执行目标对象的方法
Object reuslt = method.invoke(target, args);
System.out.println(">>>日志收集结束>>>>");
return reuslt;
}
/**
* 使用jdk动态代理创建代理类
* @param <T>
* @return
*/
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
public class JdkTest {
public static void main(String[] args) {
JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl());
OrderService proxy = jdkInvocationHandler.getProxy();
proxy.order();
}
}
- 获取代理的生成的class文件
public class JdkTest {
public static void main(String[] args) {
//获取代理的生成的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl());
OrderService proxy = jdkInvocationHandler.getProxy();
proxy.order();
}
}
注意:继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
5.2.2 CGLIB动态代理
Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。
5.2.2.1 CGLIB原理
运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。
5.2.2.2 Cglib优缺点
- 优点:JDK动态代理要求被代理的类必须实现接口,当需要代理的类没有实现接口时Cglib代理是一个很好的选择。另一个优点是Cglib动态代理比使用java反射的JDK动态代理要快
- 缺点:对于被代理类中的final方法,无法进行代理,因为子类中无法重写final函数
5.2.2.3 CGLIB代理实现
实现MethodInterceptor接口的intercept方法后,所有生成的代理方法都调用这个方法。
intercept方法的具体参数有
obj 目标类的实例
- method 目标方法实例(通过反射获取的目标方法实例)
- args 目标方法的参数
- proxy 代理类的实例
该方法的返回值就是目标方法的返回值。
public class OrderServiceImpl {
public void order() {
System.out.println("用户下单操作..");
}
}
public class CglibMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("<<<<<日志收集开始...>>>>>>>");
Object reuslt = proxy.invokeSuper(obj, args);
System.out.println("<<<<<日志收集结束...>>>>>>>");
return reuslt;
}
}
public class CglibTest {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/code/cglib");
CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor();
Enhancer enhancer = new Enhancer();
// 设置代理类的付类
enhancer.setSuperclass(OrderServiceImpl.class);
// 设置回调对象
enhancer.setCallback(cglibMethodInterceptor);
// 创建代理对象
OrderServiceImpl orderServiceImpl = (OrderServiceImpl) enhancer.create();
orderServiceImpl.order();
}
}
5.3 JDK动态代理与CGLIB实现区别
- JDK动态代理底层实现:
JDK的动态代理使用Java的反射技术生成动态代理类,只能代理实现了接口的类, 没有实现接口的类不能实现动态代理。 - CGLIB动态代理底层实现:
运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,不需要被代理类对象实现接口,从而CGLIB动态代理效率比Jdk动态代理反射技术效率要高。
5.4 使用AOP拦截Controller所有请求日志
5.4.1 添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
<!-- sprinboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
5.4.2 代码
@Aspect
@Component
@Slf4j
public class AopLogAspect {
// 申明一个切点 里面是 execution表达式
@Pointcut("execution(* com.mayikt.controller.*.*(..))")
private void serviceAspect() {
}
// 请求method前打印内容
@Before(value = "serviceAspect()")
public void methodBefore(JoinPoint joinPoint) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
// 打印请求内容
log.info("===============请求内容===============");
log.info("请求地址:" + request.getRequestURL().toString());
log.info("请求方式:" + request.getMethod());
log.info("请求类方法:" + joinPoint.getSignature());
log.info("请求类方法参数:" + Arrays.toString(joinPoint.getArgs()));
log.info("===============请求内容===============");
}
// 在方法执行完结后打印返回内容
@AfterReturning(returning = "o", pointcut = "serviceAspect()")
public void methodAfterReturing(Object o) {
log.info("--------------返回内容----------------");
log.info("Response内容:" + o.toString());
log.info("--------------返回内容----------------");
}
}
6.纯手写JDK动态代理
6.1 原理分析
- 创建代理类$Proxy0源代码文件实现被代理的接口。
- 使用JavaCompiler技术编译该 P r o x y 0 文 件 获 取 到 Proxy0文件获取到 Proxy0文件获取到Proxy0.class
- 使用ClassLoader将该$Proxy0.class加入到当前JVM内存中
ClassLoader 顾名思义就是类加载器,ClassLoader 作用:
负责将 Class 加载到 JVM 中
审查每个类由谁加载(父优先的等级加载机制)
将 Class 字节码重新解析成 JVM 统一要求的对象格式
6.2 动态代理的简单实现
public interface OrderService {
public void order() throws Throwable;
}
public class OrderServiceImpl implements OrderService {
@Override
public void order() {
System.out.println("数据库订单执行操作");
}
}
public interface MyExtJdkInvocationHandler {
/**
* @param proxy 代理类
* @param method 目标方法
* @param args 参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
public class MyJdkInvocationHandler implements MyExtJdkInvocationHandler {
/**
* 目标对象 被代理的类 真实访问的类的对象
*/
private Object target;
public MyJdkInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("<<<<<<<<<纯手写jdk动态代理日志开始>>>>>>>>>>");
// 使用java的反射执行
Object result = method.invoke(target, args);
System.out.println("<<<<<<<<纯手写jdk动态代理结束>>>>>>>>>>");
return result;
}
}
public class $Proxy0 implements OrderService {
private MyExtJdkInvocationHandler h;
public $Proxy0(MyExtJdkInvocationHandler h) {
this.h = h;
}
@Override
public void order() throws Throwable {
// 如何获取真实目标方法呢
Method orderMethod = OrderService.class.getMethod("order", new Class[]{});
this.h.invoke(this, orderMethod, null);
}
}
public class Test001 {
public static void main(String[] args) throws Throwable {
OrderService orderService = new $Proxy0(new MyJdkInvocationHandler(new OrderServiceImpl()));
orderService.order();
}
}
6.2 加入动态实现
public class JavaClassLoader extends ClassLoader {
private File classPathFile;
public JavaClassLoader(){
String classPath="/code";
this.classPathFile=new File(classPath);
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
String className= JavaClassLoader.class.getPackage().getName()+"."+name;
if(classPathFile!=null){
File classFile=new File(classPathFile,name.replaceAll("\\.","/")+".class");
if(classFile.exists()){
FileInputStream in=null;
ByteArrayOutputStream out=null;
try {
in=new FileInputStream(classFile);
out=new ByteArrayOutputStream();
byte[] buff=new byte[1024];
int len;
while ((len=in.read(buff))!=-1){
out.write(buff,0,len);
}
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
e.printStackTrace();
}finally {
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out!=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
}
public class MyProxy {
static String rt = "\r\t";
public static Object newProxyInstance(JavaClassLoader javaClassLoader,
Class<?> classInfo,
MyExtJdkInvocationHandler h) {
//1.拼接代理类的源代码
try {
// 1.创建代理类java源码文件,写入到硬盘中..
Method[] methods = classInfo.getMethods();
String proxyClass = "package com.grape.dp.proxy.myself.handler;" + rt
+ "import java.lang.reflect.Method;" + rt
+ "import com.grape.dp.proxy.myself.inter.MyExtJdkInvocationHandler;" + rt
+ "public class $Proxy0 implements " + classInfo.getName() + "{" + rt
+ "MyExtJdkInvocationHandler h;" + rt
+ "public $Proxy0(MyExtJdkInvocationHandler h)" + "{" + rt
+ "this.h= h;" + rt + "}"
+ getMethodString(methods, classInfo) + rt + "}";
// 2. 写入到到本地文件中..
String filename = "/code/$Proxy0.java";
File f = new File(filename);
FileWriter fw = new FileWriter(f);
fw.write(proxyClass);
fw.flush();
fw.close();
// 3. 将源代码编译成class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(filename);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
// 4.使用classLoader 加载到内存中..
Class<?> $Proxy0 = javaClassLoader.findClass("$Proxy0");
// 5.指明初始化有参数构造函数
Constructor<?> constructor = $Proxy0.getConstructor(MyExtJdkInvocationHandler.class);
Object o = constructor.newInstance(h);
return o;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String getMethodString(Method[] methods, Class intf) {
String proxyMe = "";
for (Method method : methods) {
proxyMe += "public void " + method.getName() + "() throws Throwable {" + rt
+ "Method md= " + intf.getName() + ".class.getMethod(\"" + method.getName()
+ "\",new Class[]{});" + rt
+ "this.h.invoke(this,md,null);" + rt + "}" + rt;
}
return proxyMe;
}
}
public class Test001 {
public static void main(String[] args) throws Throwable {
OrderService orderService = (OrderService) MyProxy.newProxyInstance(new JavaClassLoader(), OrderService.class, new MyJdkInvocationHandler(new OrderServiceImpl()));
orderService.order();
}
}