零基础学JavaWeb开发(二十)之 spring框架(3)
创始人
2024-05-15 18:37:09
0

SpringBean的AOP

1、AOP基本的概念

AOP(Aspect Oriented Programming)是一种面向切面的编程思想。面向切面编程是将程序抽象成各个切面,即解剖对象的内部,将那些影响了多个类的公共行为抽取到一个可重用模块里,减少系统的重复代码,降低模块间的耦合度,增强代码的可操作性和可维护性。

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理、增强处理。

简单理解:

Aop面向切面编程,在方法之前和之后实现处理 应用场景在于:日志打印、事务实现、安全、权限控制、自定义注解等。

因为AOP可以解决我们程序上的代码冗余问题

AOP 底层基于 代理设计模式封装

代理设计模式 静态代理与动态代理

动态代理 jdk动态代理与 cglib动态代理

通俗易懂 aop 在我们的目标方法之前和之后 处理的操作

开启事务

目标方法

提交或者回滚事务

提交或者回滚事务

aop 日志打印 事务原理 自定义实现

2、代理模式实现的原理

代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色,Proxied)以及代理类角色(Proxy),如上图所示:

抽象主题角色:可以是接口,也可以是抽象类;

委托类角色:真实主题角色,业务逻辑的具体执行者;

代理类角色:内部含有对真实对象RealSubject的引用,负责对真实主题角色的调用,并在真实主题角色处理前后做预处理和后处理。

2.1、代理模式创建方式

相关测试代码:

package com.mayikt.service;public interface OrderService {/*** 添加订单数据*/String addOrder(String orderName);
}
package com.mayikt.service.impl;import com.mayikt.service.OrderService;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class OrderServiceImpl implements OrderService {@Overridepublic String addOrder(String orderName) {log.info("", orderName);return "ok";}
}

2.2、静态代理

2.2.1、基于接口实现方式

package com.mayikt.proxy1;import com.mayikt.service.OrderService;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class OrderServiceProxy implements OrderService {private OrderService orderService;public OrderServiceProxy(OrderService orderService) {this.orderService = orderService;}@Overridepublic String addOrder(String orderName) {// 目标方法前后处理操作log.info("<目标方法之前执行...>");String result = orderService.addOrder(orderName);log.info("<目标方法之后执行...>");return result;}
}
package com.mayikt.proxy1;import com.mayikt.service.impl.OrderServiceImpl;public class Test01 {public static void main(String[] args) {OrderServiceProxy orderServiceProxy = new OrderServiceProxy(new OrderServiceImpl());String result = orderServiceProxy.addOrder("mayikt");System.out.println(result);}
}

2.2.2、基于继承实现方式

package com.mayikt.proxy2;import com.mayikt.service.impl.OrderServiceImpl;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class OrderServiceProxy  extends OrderServiceImpl {@Overridepublic String addOrder(String orderName) {// 目标方法前后处理操作log.info("<目标方法之前执行...>");String result = super.addOrder(orderName);log.info("<目标方法之后执行...>");return result;}
}
package com.mayikt.proxy2;public class Test01 {public static void main(String[] args) {OrderServiceProxy orderServiceProxy = new OrderServiceProxy();String result = orderServiceProxy.addOrder("mayikt");System.out.println(result);}
}

2.3、动态代理

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成 。

JDK动态代理的一般步骤如下:

1.创建被代理的接口和类;

2.实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;

3.调用Proxy的静态方法,创建代理类并生成相应的代理对象;

实现原理:利用拦截器机制必须实现InvocationHandler接口中的invoke方法实现对

我们的目标方法增强。

2.3.1、JDK API动态代理用法

package com.mayikt.proxy3;import lombok.extern.slf4j.Slf4j;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;@Slf4j
public class JdkInvocationHandler implements InvocationHandler {/*** 目标对象*/private Object target;public JdkInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.info(",args:{}", args);Object result = method.invoke(target, args);log.info(" T getProxy() {return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}
}
package com.mayikt.proxy3;import com.mayikt.service.OrderService;
import com.mayikt.service.impl.OrderServiceImpl;public class Test01 {public static void main(String[] args) {System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl());OrderService orderService = jdkInvocationHandler.getProxy();orderService.addOrder("mayikt");}
}

2.3.2、动态代理与静态代理的区别

动态代理不需要写代理类对象,通过程序自动生成,而静态代理需要我们自己写代理类对象。

 

2.3.3、JDK动态代理原理分析

1. 获取代理的生成的class文件

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package com.sun.proxy;import com.mayikt.service.OrderService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements OrderService {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String addOrder(String var1) throws  {try {return (String)super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.mayikt.service.OrderService").getMethod("addOrder", Class.forName("java.lang.String"));m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}注意:继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
    public static void main(String[] args) {$Proxy0 $Proxy0 = new $Proxy0(new JdkInvocationHandler(new OrderServiceImpl()));$Proxy0.addOrder("mayikt");}
  public  T getProxy() {return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);生成代理类时 会传递我们目标对象实现哪些接口
target.getClass().getInterfaces()如果我们目标对象没有实现接口 jdk动态代理生成代理类 也没有实现接口使用jdk动态代理时 注意 让目标对象实现接口, 生成代理类时 实现目标对象的接口方便可以使用接口调用目标对象的方法

1.执行到我们 代理类中$Proxy0.addOrder()

2.执行到我们MayiktInvocationHandler.invoke

  public final String addOrder(String var1) {try {return (String) super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}

3.执行到我们MayiktInvocationHandler.invoke 在根据java反射机制 传递目标对象 调用目标方法

2.3.4、mybatis mapper接口分析

在mybatis mapper 是一个接口为何可以调用的呢?底层其实就是基于JDK动态代理实现。

相关代码:

package com.mayikt.mybatis.ext;import org.springframework.stereotype.Indexed;import java.lang.annotation.*;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface MayiktInsert {String value();
}
package com.mayikt.mybatis;import com.mayikt.mybatis.ext.MayiktInsert;
import com.mayikt.utils.MayiktJdbcUtils;
import org.apache.commons.lang.StringUtils;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;public class MybatisJdkInvocationHandler implements InvocationHandler {private Class mapperClass;public MybatisJdkInvocationHandler(Class mapperClass) {this.mapperClass = mapperClass;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 使用java反射技术获取该方法上的注解MayiktInsert declaredAnnotation = method.getDeclaredAnnotation(MayiktInsert.class);String insertSql = declaredAnnotation.value();if (StringUtils.isEmpty(insertSql)) {return null;}// 执行该sql语句Connection connection = MayiktJdbcUtils.getConnection();PreparedStatement preparedStatement = connection.prepareStatement(insertSql);int result = preparedStatement.executeUpdate();return result;}public  T getProxy() {return (T) Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, this);}
}
package com.mayikt.mybatis;import com.mayikt.mybatis.ext.MayiktInsert;public interface UserMapper {@MayiktInsert("INSERT INTO `mayikt`.`mayikt_users` (`id`, `name`, `age`) VALUES (null, 'wangmazi', NULL);")int addUser();
}
package com.mayikt.mybatis;public class Test01 {public static void main(String[] args) {System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");UserMapper userMapper = MapperProxy.getUserMapper(UserMapper.class);int result = userMapper.addUser();System.out.println(result);}
}
package com.mayikt.mybatis;public class MapperProxy {public static UserMapper getUserMapper(Class mapperClass) {return new MybatisInvocationHandler(mapperClass).getProxy();}public static void main(String[] args) {// 将jdk动态生成好的 class 存放本地System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");UserMapper userMapper = MapperProxy.getUserMapper(UserMapper.class);int i = userMapper.addUser();System.out.println(i);}
}

📎mayikt-designmode.rar

2.3.5、什么是CGLIB 动态代理

Cglib 底层基于asm实现

Cglib 与jdk动态代理到底有哪些区别呢?

jdk动态代理底层基于反射方式调用目标方法 jdk7之前效率是非常低后期优化。

Cglib 底层是反射调用目标方法效率非常低 ,直接采用建立fastclass 索引的方式

调用目标方法。

jdk7之前Cglib动态代理效率是比我们jdk动态代理效率高非常多

jdk7开始jdk动态代理是我们Cglib动态代理效率高。

Cglib动态代理生成的代理类 直接 继承 被代理类(实现继承方式代理)

public class OrderServiceImpl$$EnhancerByCGLIB$$1dd3a71c extends OrderServiceImpl implements Factory {

jdk动态代理生成代理类 实现 被代理实现的接口 (实现接口方式代理)

Cglib底层生成代理类是通过 asm 生成字节码 class

1.Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。

2.运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。

3.jdk7开始 jdk动态代理效率比cglib要高

spring底层采用 cglib代理类?还是jdk动态代理

判断 被代理类 实现接口 使用jdk动态代理

如果被代理类 没有实现接口则使用cglib代理类

2.3.6、cglib基本用法

    cglibcglib3.2.12

实现MethodInterceptor接口的intercept方法后,所有生成的代理方法都调用这个方法。

intercept方法的具体参数有

obj 目标类的实例

1. method 目标方法实例(通过反射获取的目标方法实例)

2. args 目标方法的参数

3. proxy 代理类的实例

该方法的返回值就是目标方法的返回值。

package com.mayikt.service;public interface OrderService {/*** 添加订单数据*/String addOrder(String orderName);
}
package com.mayikt.service.impl;import com.mayikt.service.OrderService;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class OrderServiceImpl implements OrderService {
//    @Overridepublic String addOrder(String orderName) {log.info("", orderName);// addorder相关的事情return "ok";}
}
package com.mayikt.cglib;import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;@Slf4j
public class MayiktCglibMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {log.info("<目标方法之前开始执行....>");
//        Object result = method.invoke(obj, args);Object result = proxy.invokeSuper(obj, args);log.info("<目标方法之后开始执行....>");return result;}
}
package com.mayikt.cglib;import com.mayikt.service.impl.OrderServiceImpl;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;public class Test01 {public static void main(String[] args) {System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code\\cglib");MayiktCglibMethodInterceptor mayiktCglibMethodInterceptor = new MayiktCglibMethodInterceptor();Enhancer enhancer = new Enhancer();// 设置代理enhancer.setSuperclass(OrderServiceImpl.class);// 设置cglib 回调类enhancer.setCallback(mayiktCglibMethodInterceptor);// 创建cglib代理类OrderServiceImpl orderServiceImpl = (OrderServiceImpl) enhancer.create();String result = orderServiceImpl.addOrder("mayikt");System.out.println(result);}
}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...