Spring——面向切面编程(AOP)

2023-11-11

1 AOP概述

  AOP 并不是 Spring 框架的专属名称,它的全称是 Aspect Oriented Programming ,意为:面向切面编程。
  在程序运行某个方法的时候,不修改原始执行代码逻辑,由程序动态地执行某些额外的功能,对原有的方法做增强,这就叫做面向切面编程。
  在学习AOP之前,建议先学习一下设计模式中的代理模式。

2 术语解释

Join point(连接点)
  连接点是指那些被拦截到的点。在 Spring 中这些点指的是方法,可以看作正在访问的,或者等待访问的那些需要被增强功能的方法。Spring 只支持方法类型的连接点。

Pointcut(切入点)
  切入点是一个规则,定义了我们要对哪些 Joinpoint 进行拦截。因为在一个程序中会存在很多的类,每个类又存在很多的方法,而哪些方法会应用 AOP 对该方法做功能增强呢?这就需要依据我们配置的切入点规则。

Advice(通知)
  拦截到 Joinpoint 之后所要做的事情就是通知。 也就是对方法做的增强功能。

通知分类
  前置通知:在连接点之前运行的通知类型,它不会阻止流程进行到连接点,只是在到达连接点之前运行该通知内的行为,当然 —— 除非它引发异常;
  后置通知:在连接点正常完成后要运行的通知,正常的连接点逻辑执行完,会运行该通知,当然 —— 方法正常返回而没有引发异常;
  最终通知:无论连接点执行后的结果如何,正常还是异常,都会执行的通知;
  异常通知:如果连接点执行因抛出异常而退出,则执行此通知;
  环绕通知:环绕通知可以在方法调用之前和之后执行自定义行为。

Target (目标)
  Target 指的是代理的目标对象,也就是连接点所在的类。

Aspect(切面)
  切面本质是一个类,只不过是个功能类,作为整合 AOP 的切入点和通知。一般来讲,需要在 Spring 的配置文件中配置,或者通过注解来配置。

Weaving(织入)
  织入是一种动作的描述,在程序运行时将增强的功能代码也就是通知,根据通知的类型(前缀后缀等…)放到对应的位置,生成代理对象。

Proxy(代理)
  一个类被 AOP 织入增强后,产生的结果就是代理类

3 Spring AOP 的代理模式

  代理名词解释为:以被代理人名义,在授权范围内与第三方实施行为。而在软件行业中代理模式是一种非常常用的设计模式,跟现实生活中的逻辑一致。
  在开发中代理模式的表现为:创建带有现有对象的代理对象以便向外界提供功能接口。代理对象可以为委托对象执行一些附带的,增加的额外功能。

3.1 代理模式分类

  在开发中,实现代理模式可以分为两种。
  静态代理:若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理 ,这种情况下的代理类通常都是我们在 Java 代码中定义的, 静态代理中的代理类和委托类会实现同一接口;

  动态代理:代理类在程序运行时创建的代理方式被称为动态代理。 也就是说,这种情况下,代理类并不是在 Java 代码中定义的,而是在运行时根据我们在 Java 代码中的 “指示” 动态生成的。

3.2 静态代理示例

  userService 接口代码

public interface UserService {
   
    public void saveUser();
}

  userServiceImpl 实现类代码

@Service
public class UserServiceImpl implements  UserService {
   

    public void saveUser() {
   

        System.out.println("执行service中的保存逻辑");
    }
}

  userServiceProxy 代理类代码

public class UserServiceProxy implements UserService {
   
    private UserService userService;

    public UserServiceProxy(UserService userService) {
   
        this.userService = userService;
    }

    @Override
    public void saveUser() {
   
        System.out.println("委托类执行saveUser方法之前的逻辑代码");
        userService.saveUser();
        System.out.println("委托类执行saveUser方法之后的逻辑代码");
    }
}

代码解析:
  userServiceProxy 代理类中的属性为委托类的接口对象,目的是在构造方法中接收委托类实例,对实例方法做功能增强。

  saveUser 方法是代理类执行的逻辑,在方法内部有增强的代码逻辑,也保留了原始实例的代码功能。

测试代码:

public class test {
   
    public static void main(String[] args) {
   
        AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();
        context.register(SpringConfig.class);
        context.refresh();
        UserService userService=context.getBean(UserService.class);
        UserServiceProxy proxy=new UserServiceProxy(userService);
        proxy.saveUser();
    }
}

运行结果
在这里插入图片描述
  可以看到,执行结果中即包含了被代理对象的原始保存方法的逻辑,也有代理类中对原始方法的两个增强代码。

3.3 动态代理示例

  创建动态处理器

public class DynamicProxy implements InvocationHandler {
   
    private Object object;

    public DynamicProxy(Object object) {
   
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
        System.out.println("执行前逻辑");
        Object result = method.invoke(object, args);
        System.out.println("执行后逻辑");
        return result;
    }
}

  测试类代码

public class test {
   
    public static void main(String[] args) {
   
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(SpringConfig.class);
        context.refresh();
        // 获取接口实例
        UserService userService = context.getBean(UserService.class);
        // 动态创建实例的代码
        UserService proxy = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),
                new Class[]{
   UserService.class}, new DynamicProxy(userService));
        // proxy执行方法
        proxy.saveUser();
    }
}

代码解析
  Proxy.newProxyInstance 是 JDK 提供的一个用于动态创建代理实例的方法,参数解释如下:
  ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法是固定的。
  Class<?>[] interfaces: 指定目标对象实现的接口的类型,使用泛型方式确认类型。
InvocationHandler: 指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法。

运行结果
在这里插入图片描述

4 Spring AOP 实现之 XML 配置

4.1 工程搭建介绍

数据库建表 SQL:

CREATE TABLE `account` (
  `id` int(11) NOT NULL auto_increment COMMENT 'id',
  `accountNum` varchar(20) default NULL COMMENT '账号',
  `money` int(8) default NULL COMMENT '余额',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

工程代码介绍
  实体类: 跟数据库表对应的 Java 类 Account ;
  操作实体类的: Dao 和 Dao 的接口实现类 ;
  调用持久层的业务类: Service 和 Service 的实现类 ;
  事务管理器类: TransactionManager 提供事务的一系列操作 ;
  测试代码类: 初始化 Spring 调用类中的方法测试 。

AOP 中的核心概念
在这里插入图片描述
  所以:在对原始业务类中的方法执行之前的增强行为就是前置通知,在对原始业务类中的方法执行之后的增强行为就是后置通知。而一旦出现异常,那么所做的动作就是异常通知。本案例使用几种通知,来实现事务的控制。

4.2 代码实现

创建 maven 工程:省略

pom 文件的依赖坐标如下:

     <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.17</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.17</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring——面向切面编程(AOP) 的相关文章

  • 单击链接时如何将另一个 JSP 页面注入到

    我在一个JSP页面中有两个不同的部分 其中一个包含链接菜单 单击时 div2 id content 会相应加载不同的页面 我正在做类似的事情 div ul class navbar li a href Login jsp Login a l
  • 将倒计时器从 10 秒改为 1 秒

    我有一个倒计时器 它以 1 秒的增量从 10000 毫秒倒计时到 0 毫秒 以使按钮在 10 秒后可单击 尽管计时器是准确的并且按照代码的说明执行操作 但我想更改秒的表示方式 但我不知道如何更改 java void startTimer c
  • Java 8 中异常类型推断的一个独特功能

    在为该网站上的另一个答案编写代码时 我遇到了这个特性 static void testSneaky final Exception e new Exception sneakyThrow e no problems here nonSnea
  • JTree 避免重新加载后崩溃

    我正在尝试找到解决崩溃问题的方法JTree重新加载后 情况 JTree Office A Office A 1 Office A 1 1 Office A 1 2 Office B Office B 1 Office B 1 1 Offic
  • android.os.FileUriExposedException 在 Oreo 中引起(仅!)[重复]

    这个问题在这里已经有答案了 从 Google Play Console 中 我可以看到此异常仅发生在 Android 8 0 的设备上 android os FileUriExposedException at android os Str
  • LibGDX 闪烁

    我已经使用 LibGDX UI 设置来启动一个项目 我在实现 ApplicationListener 中唯一拥有的是 public void create setScreen new LoadingScreen this 这应该会触发 Lo
  • 枚举内的枚举

    这不是我被卡住的问题 而是我正在寻找一种简洁的方式来编写我的代码 本质上 我正在编写一个事件驱动的应用程序 用户触发事件 事件被发送到适当的对象 然后对象处理事件 现在我正在编写偶数处理程序方法 我希望使用 switch 语句来确定如何处理
  • 如何使用jdbc驱动编写事务?

    我想使用 jdbc 编写一个事务java 我尝试过这个简单的交易 BEGIN TRANSACTION NL GO NL UPDATE table SET col test where id 1010 NL GO NL COMMIT 我尝试过
  • MongoDb Spring 在嵌套对象中查找

    我正在使用 Spring Data Mongodb 和这样的文档 id ObjectId 565c5ed433a140520cdedd7f attributes 565c5ed433a140520cdedd73 333563851 list
  • 当对话框打开时如何处理屏幕方向变化?

    我有一个 Android 应用程序 它已经在处理方向的更改 即有一个android configChanges orientation 在清单和onConfigurationChange 活动中的处理程序切换到适当的布局并准备它 我有一个横
  • Spring portlet mvc:@Valid 似乎不起作用

    我创建了一个 bean 类并在我的控制器中使用它 但它似乎不起作用 也就是说 即使我输入了无效的年龄 result hasErrors仍然是假的 豆类 public class User Min 13 private int age pri
  • 我有什么理由应该嘲笑?

    我也是 Mockito 和 PowerMockito 的新手 我发现我无法使用纯 Mockito 测试静态方法 因此我需要使用 PowerMockito 对吗 我有一个非常简单的类 名为 Validate 使用这个非常简单的方法 publi
  • 抛出 UnsupportedOperationException

    因此其中一种方法的描述如下 public BasicLinkedList addToFront T data 该操作无效 对于排序列表 将生成 UnsupportedOperationException 使用消息 排序列表的操作无效 我的代
  • Java如何区分这些具有相同名称/签名的多个方法?

    今天我在追踪一个错误 我注意到我们的一个班级中有一些奇怪的事情 我删除了尽可能多的代码并发布在这里 class A static int obtainNumber return 42 static int obtainNumber retu
  • java中从视频中提取图像

    我想知道如何使用 JMF 从视频中提取图像 Player player Manager createRealizedPlayer cdi getLocator player start FrameGrabbingControl frameG
  • Maven编译错误:包不存在

    我正在尝试向现有企业项目添加 Maven 支持 这是一个多模块项目 前 2 个模块编译和打包没有问题 但我面临编译错误 我尝试在多个模块中使用相同的依赖项 我的结构是 gt parent gt pom xml gt module 1 gt
  • 如何强制 Spark 执行代码?

    我如何强制 Spark 执行对 map 的调用 即使它认为由于其惰性求值而不需要执行它 我试过把cache 与地图调用 但这仍然没有解决问题 我的地图方法实际上将结果上传到 HDFS 所以 它并非无用 但 Spark 认为它是无用的 简短回
  • 使用 System.out.println 显示特殊字符

    我在将带有特殊字符的文本从网络服务发送或显示到数据库时遇到问题 在我的 Eclipse 上 我已将字符编码设置为 UTF 8 但它仍然不允许我显示字符 例如 像下面的代码一样简单的打印 String test System out prin
  • 如何在 logback 中启动时滚动日志文件

    我想配置 logback 来执行以下操作 记录到文件 当文件达到 50MB 时滚动文件 仅保留 7 天的日志 启动时始终生成一个新文件 滚动 除了最后一项 启动卷 外 我一切都正常 有谁知道如何实现这一目标 这是配置
  • 仅当用户开始输入时清除 JavaFX TextField 中的提示文本

    默认行为是当字段获得焦点时 字段中的提示文本将被删除 那是标记在场上的时候 是否可以配置文本字段 以便仅在用户开始输入时删除提示文本 否则 我需要在每个文本字段旁边 上方添加一个标签 以描述其中的值 我知道它有点旧 但我自己也需要它 这仍然

随机推荐