前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >面向切面的Spring

面向切面的Spring

作者头像
端碗吹水
发布于 2020-09-23 06:01:07
发布于 2020-09-23 06:01:07
68300
代码可运行
举报
运行总次数:0
代码可运行

前言

转眼间,快到夏天了,又让我想起来往年盛夏时,被空调、西瓜、冰淇淋支配的恐惧,南方的天气是真的热,在这种天气下,西瓜、冰淇淋可以没有,但是空调是必不可少的。但是空调的缺点是耗电,而电需要钱(这不废话吗)。为了享受凉爽和舒适,我们没有什么办法可以避免这种开销。这是因为每家每户都有一个电表来记录用电量,每个月都会有人来查电表(不是查水表就行),这样电力公司就知道应该收取多少费用了,用户也没办法赖账。

现在想象一下,如果没有电表,也没有人来查看用电量,假设现在由用户来联系电力公司并报告自己的用电量。虽然可能会有一些特别执着的用户会详细记录使用电灯、电视以及空调的情况,但大多数人肯定不会这么做。基于信用的电力收费对于消费者来说可能非常不错,但对于电力公司来说结果可能就不那么美妙了。

监控用电量是一个很重要的功能,但并不是大多数家庭关注的问题。所有家庭实际上所关注的可能是打扫卫生、更换电器、购买每天的食材等事项。从家庭的角度来看,监控房屋的用电量是一个被动事件。

软件系统中的一些功能就像我们家里面的电表一样。这些功能需要用到应用程序的多个地方,但是我们又不想在每个点都明确调用它们,因为这样会导致类似的代码重复出现在各个地方。日志、安全和事务管理的确都很重要,但它们是否为应用对象主动参与的行为呢?如果让应用对象只关注于自己所针对的业务逻辑问题,而其他方面的问题由其他应用对象来处理,这会不会更好?就好比让电表来帮我监控用电量,让电力公司来按电表记录收取费用即可,而我们不需要去过多的关心这些问题。

软件开发中,散布于应用多处的功能被称为横切关注点(cross-cutting concern)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。


什么是面向切面编程

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务等。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来,将横切关注点进行模块化。

如前所述,切面能帮助我们模块化横切关注点。简而言之,横切关注点可以被描述为影响应用多处的功能。例如,安全就是一个横切关注点,应用中的许多方法都会涉及到安全规则,毕竟客户端所输入的数据永远是不可信的。下图直观呈现了横切关注点的概念:

上图展现了一个被划分为模块的典型应用。每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能,例如安全和事务管理。

如果要复用通用功能的话,最常见的面向对象的技术是继承(inheritance)或委托(delegation)。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系,面向对象的设计原则之一的合成/聚合复用原则,就强调了尽量使用合成/聚合,尽量不要使用类继承;而使用委托可能需要对委托对象进行复杂的调用。

切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简介。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类。以上也提到了横切关注点可以被模块化为特殊的类,这些类就被称为切面。这样做有两个好处:首先,现在每个关注点都集中于一个地方,而不是分散到多处代码中,其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。


定义 AOP 术语

与大多数技术一样,AOP已经形成了自己的术语。描述切面的常用术语有通知(advice)、切点(pointcut)和连接点(join point),下图展示了这些概念是如何关联在一起的:

遗憾的是,大多数用于描述AOP功能的术语并不直观,尽管如此,它们现在已经是AOP“行话"的组成部分了,为了理解AOP,我们必须了解这些术语。在我们进入某个领域之前,必须学会在这个领域该如何说话。

通知(Advice)

当抄表员出现在我们家门口时,它们要登记用电量并回去向电力公司报告。显然,它们必须有一份需要抄表的住户清单,它们所汇报的信息也很重要,但记录用电量才是抄表员的主要工作。

类似的,切面也有目标——它必须要完成的工作。在AOP术语中,切面的工作被称为通知

通知定义了切面是什么以及何时调用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。它应该在某个方法被调用之前?之后?之前和之后都调用?还是只在方法抛出异常时调用?

Spring切面可以应用5种类型的通知:

  • 前置通知(Before):在目标方法被调用之前调用通知功能
  • 后置通知(After):在目标方法被调用之后调用通知,此时不会关心方法的输出是什么
  • 返回通知(After-returning):在目标方法成功执行之后调用通知
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义行为

连接点(Join point)

电力公司为多个住户提供服务,甚至可能是整个城市。每家都有一个电表,这些电表上的数字都需要读取,因此每家都是抄表员的潜在目标。抄表员也许能够读取各种类型的设备,但是为了完成他的工作,他的目标应该是房屋内所安装的电表。

同样,我们的应用可能也有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

切点(Pointct)

如果让一位抄表员访问电力公司所服务的所有住户,那肯定是不现实的。实际上,电力公司为每一个抄表员都分别指定某一块区域的住户。类似的,一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。

如果说通知定义了切面的 “什么” 和 “何时” 的话,那么切点就定义了 “何处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根据运行时的决策(比如方法中的参数值)来决定是否应用通知。

切面(Aspect)

当抄表员开始一天的工作时,他知道自己要做的事情(报告用电量)和从哪些房屋收集信息。因此,他知道要完成工作所需要的一切东西。

切面就是通知和切点的结合,通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。

引入(Introduction)

引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简单,只需要一个方法,setLastModified(Date date),和一个实例变量来保存这个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从而可以在无需修改这些现有的类的情况下,让他们具有新的行为和状态。

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入的。
  • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5 的加载时织入(load-time weaving,LTW)就支持这种方式织入切面。
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态的创建一个代理对象,也就是Java中的动态代理模式。Spring AOP就是以这种方式织入切面的。

想学好AOP面向切面编程,要掌握的东西可不少,虽然Spring AOP框架帮我们简化了这种面向切面编程的方式,但是我们得学习其中的一些基本的概念,可以的话最好研究其实现方式(读源码),不能只知其然而不知其所以然。

现在我们已经了解了如下知识点:

  • 通知包含了需要用于多个用于对象的横切行为
  • 连接点是程序执行过程中能够应用通知的所有点
  • 切点定义了通知被应用的具体位置(在哪些连接点)
  • 切面是通知和切点的结合,通知和切点共同定义了切面的全部内容
  • 引入允许我们在不修改现有类的前提下,向现有的类添加新方法或属性
  • 织入是把切面应用到目标对象并创建新的代理对象的过程

其中关键概念是切点定义了哪些连接点会得到通知。


Spring对AOP的支持

创建切点来定义切面所织入的连接点是AOP框架的基本功能,Spring和AspectJ之间有大量的协作,而且Spring对AOP的支持在很多方面借鉴了AspectJ。

Spring提供了4种类型的AOP支持:

  • 基于代理的经典Spring AOP
  • 纯pojo切面
  • 使用 @AspectJ 注解驱动的切面
  • 注入式AspectJ切面(适用于Spring各版本)

前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理之上,因此,Spring对AOP的支持局限于方法拦截。

Spring的经典AOP模块并不怎么样,虽然曾经的它的确非常棒。但是现在Spring提供了更简洁和干净的面向切面编程方式。引入了简单的声明式AOP和基于注解的AOP之后,Spring经典的AOP看起来就显得非常笨重和过于复杂,所以现在基本都不再使用Spring经典的AOP方式进行面向切面编程了,本文中也不会再介绍经典的Spring AOP。

Spring AOP框架的一些关键知识点:

  • Spring通知是Java编写的,所以不需要特殊的开发环境就能开发切面,定义通知所应用的切点可以通过注解或xml来进行配置,通常情况下注解比较常用
  • Spring是在程序运行时通知对象,通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中,所以我们不需要特殊的编译器来织入Spring AOP的切面
  • 因为Spring的AOP基于动态代理,所以Spring只支持方法级别的连接点,不过方法级别的拦截已经可以满足大部分的需求。如果需要方法拦截之外的连接点拦截功能,那么我们就需要使用AspectJ来补充Spring AOP的功能。

Spring AOP的基本概念我们了解得差不多了,下面来简单介绍一下如何使用Spring AOP创建切面:

首先配置依赖的jar包,我这里使用的是maven工程,pom.xml配置内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.14.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.44</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>

Spring配置文件内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       ">

    <context:annotation-config/>
    <context:component-scan base-package="org.zero01"/>
    <aop:aspectj-autoproxy/>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          p:driverClass="com.mysql.jdbc.Driver"
          p:jdbcUrl="jdbc:mysql:///school"
          p:user="root"
          p:password="your_password"
          p:maxPoolSize="10"
          p:minPoolSize="1"
    />

</beans>

先来介绍几个注解的作用,最后我们会使用AOP来编写一个数据库的事务控制: 1. @Aspect 注解用于定义一个切面类,示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.aop;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect // 定义这是一个切面类
@Component
public class TransactionAOP {
}

在介绍其他注解之前,先说明一下如何编写切点,在Spring AOP中的切点是使用AspectJ的切点表达式来定义的。最重要的一点就是Spring仅支持AspectJ切点指示器的一个子集,因为Spring是基于代理的,而某些切点表达式是与基于代理的AOP无关的。

下表列出了Spring AOP所支持的AspectJ切点指示器:

AspectJ 指示器

描述

args()

限制连接点匹配参数为执行类型的执行方法

@args()

限制连接点匹配参数由执行注解标注的执行方法

execution()

用于匹配是连接点的执行方法

this()

限制连接点匹配AOP代理的Bean引用类型为指定类型的Bean

target()

限制连接点匹配目标对象为指定类型的类

@target()

限制连接点匹配目标对象被指定的注解标注的类

within()

限制连接点匹配匹配指定的类型

@within()

限制连接点匹配指定注解标注的类型

@annotation

限制匹配带有指定注解的连接点

以上Spring所支持的指示器中,只有execution()指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的,所以execution()指示器是我们编写切点时最主要使用的指示器,其他的指示器则只有需要限制匹配的切点时才会使用。

几个常用的 execution 示器表达式介绍:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
匹配所有

       execution("* *.*(..)")

匹配所有以set开头的方法

        execution("* *.set*(..))

匹配com包下所有的方法

        execution("* com.david.biz.service.impl.*(..))

匹配com包以及其子包下的所有方法

        execution("* com.david..*(..)")

匹配com包以及其子包下 参数类型为String 的方法

        execution("* com.david..*(java.lang.String))

为了介绍Spring的切面,我们需要有个主题来定义切面的切点。为此,我们定义一个DAO接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.dao;

import org.zero01.pojo.Student;

import java.util.List;

public interface DAO {

    public int insert(Student student);

    public int delete(int sid);

    public List<Student> selectAll();

    public int update(Student student);

}

DAO 可以包含数据库的增删查改,我们希望这些方法在被调用时能够触发切点所匹配的通知,这样就可以进行事务的控制了,所以这些方法都可以是切点。

DAO的实现类,StudentDAO代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.dao;

import org.springframework.stereotype.Component;
import org.zero01.pojo.Student;

import java.util.List;

@Component("stuDAO")
public class StudentDAO implements DAO{
    public int insert(Student student) {
        System.out.println("insert 方法执行了");
        return 0;
    }

    public int delete(int sid) {
        System.out.println("delete 方法执行了");
        return 0;
    }

    public List<Student> selectAll() {
        System.out.println("selectAll 方法执行了");
        return null;
    }

    public int update(Student student) {
        System.out.println("update 方法执行了");
        return 0;
    }
}

Student表格的字段封装类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.pojo;

import org.springframework.stereotype.Component;

@Component("stu")
public class Student {

    private int sid;
    private String sname;
    private int age;
    private String sex;
    private String address;

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

2. @Before 注解让通知方法在目标方法调用之前执行,属于前置通知,示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAOP {

    @Before("execution(* org.zero01.dao.DAO.insert(..))")
    public void testBefore(){
        System.out.println("@Before 我在目标方法调用前执行");
    }
}

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.zero01.dao.DAO;
import org.zero01.pojo.Student;

public class Test {

    public static void main(String[] args) {

        ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
        DAO dao = (DAO) app.getBean("stuDAO");
        Student student = (Student) app.getBean("stu");
        // 调用配置了切点的方法
        dao.insert(student);
    }
}

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Before 我在目标方法调用前执行
insert 方法执行了

3. @After 注解让通知方法在目标方法调用完毕之后或者抛出异常时执行,属于后置通知,示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAOP {

    @After("execution(* org.zero01.dao.DAO.insert(..))")
    public void testAfter() {
        System.out.println("@After 我在目标方法调用完毕后执行");
    }
}

测试代码和之前一样,略。

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
insert 方法执行了
@After 我在目标方法调用完毕后执行

4. @AfterReturning 注解让通知方法在目标方法调用完毕之后执行,属于返回通知,它与 @After 注解主要的区别在于当目标方法抛出异常时,使用@AfterReturning配置的方法是不会执行的,而@After 配置的方法会执行,示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.aop;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAOP {

    @AfterReturning("execution(* org.zero01.dao.DAO.insert(..))")
    public void testAfterReturning() {
        System.out.println("@AfterReturning 我在目标方法调用完毕后执行");
    }
}

测试代码和之前一样,略。

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
insert 方法执行了
@AfterReturning 我在目标方法调用完毕后执行

5. @AfterThrowing 注解让通知方法在目标方法抛出异常时执行,属于异常通知,示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.aop;

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAOP {

    @AfterThrowing("execution(* org.zero01.dao.DAO.insert(..))")
    public void testAfterThrowing() {
        System.out.println("@AfterThrowing 我在目标方法调用完毕后执行");
    }
}

StudentDAO的insert方法修改如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int insert(Student student) {
        System.out.println("insert 方法执行了");
        throw new NullPointerException();
    }

测试代码和之前一样,略。

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
insert 方法执行了
异常打印......
@AfterThrowing 我在目标方法抛出异常时执行

所以 @AfterReturning 和 @AfterThrowing 合体后就是 @After。

6. @Around 注解让通知方法在目标方法调用前后都执行,属于环绕通知,示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAOP {

    @Around("execution(* org.zero01.dao.DAO.insert(..))")
    public int testAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("@Around 我在目标方法调用前执行");

        // 把调用传递到目标方法上
        proceedingJoinPoint.proceed();

        System.out.println("@Around 我在目标方法调用后执行");

        return 0;
    }
}

如上代码中,可以看到 @Around 注解的使用方式和之前所介绍到的注解就有些不同了,首先该方法的返回值要和目标方法的返回值一致,然后就是需要接收 ProceedingJoinPoint 类型的参数,Spring会在织入切点时自动将这个参数传递进来。除此之外还需要通过调用该参数对象的 proceed 方法将调用传递到目标方法上,这一点类似于Servlet技术中Filter过滤器的doFilter方法。

这个 @Around 注解之所以能够做到环绕通知,就是因为可以在 proceed 方法的前后写上代码,这样就把 proceed 方法围起来了形成一个环绕通知,就如上的面的示例。

测试代码和之前一样,略。

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Around 我在目标方法调用前执行
insert 方法执行了
@Around 我在目标方法调用后执行

要注意的是当目标方法抛出异常时,proceed 方法下面的代码是不会被执行的,例如:

StudentDAO的insert方法修改如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int insert(Student student) {
        System.out.println("insert 方法执行了");
        throw new NullPointerException();
    }

切面代码以及测试代码和之前一样,略。

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Around 我在目标方法调用前后都执行
insert 方法执行了
异常打印......

之前我们提到过,Spring的AOP是基于动态代理的,那么我们就来看看拿出来的是不是代理对象,测试代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.zero01.dao.DAO;

public class Test {

    public static void main(String[] args) {

        ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
        DAO dao = (DAO) app.getBean("stuDAO");

        System.out.println(dao.getClass().getName());
    }
}

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
com.sun.proxy.$Proxy7

从运行结果中可以看到,打印出来的是典型的代理对象名称。但是这只是其中一种情况,因为StudentDAO实现了DAO接口,所以这时候拿出来的是实现了该接口的代理对象。现在我把StudentDAO的实现语句去掉之后,看看拿出来的是否还是代理对象。测试代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public static void main(String[] args) {

        ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
        StudentDAO dao = (StudentDAO) app.getBean("stuDAO");

        System.out.println(dao.getClass().getName());
    }

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
org.zero01.dao.StudentDAO

如上,打印的结果并不是代理对象,这是因为StudentDAO没有实现的接口,这时产生的代理类是继承于StudentDAO的,所以拿出StudentDAO对象的时候并不是一个代理对象。

7. @Pointcut 注解能够在一个切面类里定义可复用的切点,例如以上我们代码中写的指示器表达式基本都是一样的,这时候我们就可以使用 @Pointcut 注解来定义一个可复用的切点,示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAOP {

    @Pointcut("execution(* org.zero01.dao.StudentDAO.insert(..))")
    public void dao() {
    }

    @Before("dao()")
    public void testBefore(){
        System.out.println("@Before 我在目标方法调用前执行");
    }

    @After("dao()")
    public void testAfter() {
        System.out.println("@After 我在目标方法调用完毕后执行");
    }

    @AfterReturning("dao()")
    public void testAfterReturning() {
        System.out.println("@AfterReturning 我在目标方法调用完毕后执行");
    }

    @AfterThrowing("dao()")
    public void testAfterThrowing() {
        System.out.println("@AfterThrowing 我在目标方法调用完毕后执行");
    }

    @Around("dao()")
    public int testBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("@Around 我在目标方法调用前执行");

        // 把调用传递到目标方法上
        proceedingJoinPoint.proceed();

        System.out.println("@Around 我在目标方法调用后执行");

        return 0;
    }
}

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.zero01.dao.DAO;
import org.zero01.pojo.Student;

public class Test {

    public static void main(String[] args) {

        ApplicationContext app = new ClassPathXmlApplicationContext("app.xml");
        DAO dao = (DAO) app.getBean("stuDAO");
        Student student = (Student) app.getBean("stu");
        dao.insert(student);
    }
}

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Around 我在目标方法调用前执行
@Before 我在目标方法调用前执行
insert 方法执行了
@Around 我在目标方法调用后执行
@After 我在目标方法调用完毕后执行
@AfterReturning 我在目标方法调用完毕后执行

从这个打印结果中,我们还可以看到这些注解的优先级。

把以上所介绍到的注解总结成表如下:

注解

作用

@Aspect

用于定义一个切面类

@Before

让通知方法在目标方法调用之前执行

@After

让通知方法在目标方法调用完毕之后或者抛出异常时执行

@AfterReturning

让通知方法在目标方法调用完毕之后执行

@AfterThrowing

让通知方法在目标方法抛出异常时执行

@Around

让通知方法在目标方法调用前后都执行


小例题:利用Spring AOP实现简单的数据库事务控制:

1.需求:

  • 现在有一个school库,里面有一张student表以及一张studentLog表,student表用于记录学生信息,studentLog表则用于记录student表的日志信息。要求对student表进行操作时,将操作信息记录日志到studentLog表里,并且要有事务控制,当用户对student表操作失败或程序出现异常时,事务需要进行回滚,两张表都不能写入数据,必须保持两张表的数据一致。
  • student表结构如下:
  • studentLog表结构如下:

2.编写两张表格字段的封装类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.pojo;

import org.springframework.stereotype.Component;

@Component("stu")
public class Student {

    private int sid;
    private String sname;
    private int age;
    private String sex;
    private String address;

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

package org.zero01.pojo;

import java.util.Date;

public class StudentLog {

    private int log_id;
    private int sid;
    private String sname;
    private int age;
    private String sex;
    private String address;
    private String operation_type;
    private Date log_time;

    public int getLog_id() {
        return log_id;
    }

    public void setLog_id(int log_id) {
        this.log_id = log_id;
    }

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getOperation_type() {
        return operation_type;
    }

    public void setOperation_type(String operation_type) {
        this.operation_type = operation_type;
    }

    public Date getLog_time() {
        return log_time;
    }

    public void setLog_time(Date log_time) {
        this.log_time = log_time;
    }
}

3.编写数据层以及逻辑层的接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.dao;

import org.zero01.pojo.Student;

import java.util.List;

public interface DAO {

    public int insert(Student student) throws Exception;

    public int delete(int sid) throws Exception;

    public Student selectById(int sid) throws Exception;

    public List<Student> selectAll() throws Exception;

    public int update(Student student) throws Exception;

}

package org.zero01.dao;

import org.zero01.pojo.StudentLog;

import java.util.List;

public interface LogDAO {

    public int insert(StudentLog studentLog)throws Exception;

    public int delete(int log_id)throws Exception;

    public List<StudentLog> selectAll()throws Exception;

    public int update(StudentLog studentLog)throws Exception;

}

package org.zero01.service;

import org.zero01.pojo.Student;

import java.util.List;

public interface School {

    public int enterSchool(Student student) throws Exception;

    public int deleteStudentData(int sid) throws Exception;

    public Student searchStudentData(int sid) throws Exception;

    public List<Student> searchStudentsData() throws Exception;

    public int alterStudentData(Student student) throws Exception;

}

4.编写切面类,控制数据库事务:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@Aspect
@Component("tranAOP")
public class TransactionAOP {

    private final DataSource dataSource;

    @Autowired
    public TransactionAOP(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    // 保存连接对象的池子
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

    public ThreadLocal<Connection> getThreadLocal() {
        return threadLocal;
    }

    @Pointcut("execution(* org.zero01.service.*.*(..))")
    private void dao() {
    }

    /**
    * @Description: 控制数据库事务
    * @Param: 
    * @return: 
    * @Author: 01
    * @Date: 2018/3/6
    */ 
    @Around("dao()")
    public Object tranController(ProceedingJoinPoint proceedingJoinPoint) throws SQLException {

        Connection connection = null;
        Object result = null;
        try {
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            threadLocal.set(connection);
            // 把调用传递到目标方法上
            result = proceedingJoinPoint.proceed();

            connection.commit();
        } catch (Throwable t) {
            if (connection != null) {
                connection.rollback();
                t.printStackTrace();
            }
        } finally {
            if (connection != null) {
                connection.setAutoCommit(true);
                connection.close();
            }
        }
        return result;
    }
}

5.编写数据层的实现类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.zero01.aop.TransactionAOP;
import org.zero01.pojo.Student;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

@Component("stuDAO")
public class StudentDAO implements DAO {

    @Autowired
    private TransactionAOP trabAOP;

    /**
     * @Description: 添加学生数据
     * @Param: 表格的字段封装对象
     * @return: 返回插入行的id
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int insert(Student student) throws SQLException {

        Connection connection = trabAOP.getThreadLocal().get();

        String sql = "INSERT INTO student(sname,age,sex,address) VALUES (?,?,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, student.getSname());
        preparedStatement.setInt(2, student.getAge());
        preparedStatement.setString(3, student.getSex());
        preparedStatement.setString(4, student.getAddress());
        preparedStatement.executeUpdate();

        ResultSet resultSet = connection.createStatement().executeQuery("SELECT LAST_INSERT_ID()");

        if (resultSet.next()) {
            return resultSet.getInt(1);
        }

        return 0;
    }

    /**
     * @Description: 删除某个学生数据
     * @Param:  要删除行的id
     * @return: 返回影响的行数
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int delete(int sid) throws SQLException {
        Connection connection = trabAOP.getThreadLocal().get();

        String sql = "DELETE FROM student WHERE sid=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, sid);

        return preparedStatement.executeUpdate();
    }

    /**
    * @Description: 按id查找某个学生的数据
    * @Param: 要查询行的id
    * @return: 返回查询出来的学生数据
    * @Author: 01
    * @Date: 2018/3/6
    */
    public Student selectById(int sid) throws SQLException {
        Connection connection = trabAOP.getThreadLocal().get();

        String sql = "SELECT * FROM student WHERE sid=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, sid);

        ResultSet resultSet = preparedStatement.executeQuery();
        if (resultSet.next()) {
            Student student = new Student();
            student.setSid(resultSet.getInt("sid"));
            student.setSname(resultSet.getString("sname"));
            student.setAge(resultSet.getInt("age"));
            student.setSex(resultSet.getString("sex"));
            student.setAddress(resultSet.getString("address"));

            return student;
        }
        return null;
    }

    /**
    * @Description: 查询全部学生的数据
    * @return: 返回查询出来的数据集合
    * @Author: 01
    * @Date: 2018/3/6
    */
    public List<Student> selectAll() throws Exception {

        Connection connection = trabAOP.getThreadLocal().get();

        String sql = "SELECT * FROM student";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet resultSet = preparedStatement.executeQuery();

        List<Student> logList = new ArrayList<Student>();
        while (resultSet.next()) {
            Student student = new Student();

            student.setSid(resultSet.getInt("sid"));
            student.setSname(resultSet.getString("sname"));
            student.setAge(resultSet.getInt("age"));
            student.setSex(resultSet.getString("sex"));
            student.setAddress(resultSet.getString("address"));

            logList.add(student);
        }

        return logList;
    }

    /**
    * @Description: 修改某个学生的数据
    * @Param: 表格的字段封装对象
    * @return: 返回影响行数
    * @Author: 01
    * @Date: 2018/3/6
    */
    public int update(Student student) throws SQLException {

        Connection connection = trabAOP.getThreadLocal().get();

        String sql = "UPDATE student SET sname=?,age=?,sex=?,address=? WHERE sid=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, student.getSname());
        preparedStatement.setInt(2, student.getAge());
        preparedStatement.setString(3, student.getSex());
        preparedStatement.setString(4, student.getAddress());
        preparedStatement.setInt(5, student.getSid());

        return preparedStatement.executeUpdate();
    }
}

package org.zero01.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.zero01.aop.TransactionAOP;
import org.zero01.pojo.StudentLog;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

@Component("stuLogDAO")
public class StudentLogDAO implements LogDAO {

    @Autowired
    private TransactionAOP trabAOP;

    /**
    * @Description: 添加日志记录
    * @Param: 表格的字段封装对象
    * @return: 返回影响行数
    * @Author: 01
    * @Date: 2018/3/6
    */
    public int insert(StudentLog studentLog) throws Exception {

        Connection connection = trabAOP.getThreadLocal().get();

        String sql;
        PreparedStatement preparedStatement;

        if (studentLog.getOperation_type().equals("selectAll")) {
            sql = "INSERT INTO studentlog(operation_type,log_time) VALUES ('selectAll',sysdate())";
            preparedStatement = connection.prepareStatement(sql);

            return preparedStatement.executeUpdate();
        }

        sql = "INSERT INTO studentlog(sid,sname,age,sex,address,operation_type,log_time) VALUES (?,?,?,?,?,?,sysdate())";
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, studentLog.getSid());
        preparedStatement.setString(2, studentLog.getSname());
        preparedStatement.setInt(3, studentLog.getAge());
        preparedStatement.setString(4, studentLog.getSex());
        preparedStatement.setString(5, studentLog.getAddress());
        preparedStatement.setString(6, studentLog.getOperation_type());

        return preparedStatement.executeUpdate();
    }

    /**
     * @Description: 删除日志记录
     * @Param: 要删除行的id
     * @return: 返回影响行数
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int delete(int log_id) throws Exception {
        Connection connection = trabAOP.getThreadLocal().get();

        String sql = "DELETE FROM studentlog WHERE log_id=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, log_id);

        return preparedStatement.executeUpdate();
    }

    /**
     * @Description: 查询全部日志记录
     * @return: 返回查询出来的数据集合
     * @Author: 01
     * @Date: 2018/3/6
     */
    public List<StudentLog> selectAll() throws Exception {
        Connection connection = trabAOP.getThreadLocal().get();

        String sql = "SELECT * FROM studentlog";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet resultSet = preparedStatement.executeQuery();

        List<StudentLog> logList = new ArrayList<StudentLog>();
        while (resultSet.next()) {
            StudentLog studentLog = new StudentLog();
            studentLog.setLog_id(resultSet.getInt("log_id"));
            studentLog.setSid(resultSet.getInt("sid"));
            studentLog.setSname(resultSet.getString("sname"));
            studentLog.setAge(resultSet.getInt("age"));
            studentLog.setSex(resultSet.getString("sex"));
            studentLog.setAddress(resultSet.getString("address"));
            studentLog.setOperation_type(resultSet.getString("operation_type"));
            studentLog.setLog_time(resultSet.getTimestamp("log_time"));

            logList.add(studentLog);
        }

        return logList;
    }

    /**
     * @Description: 修改某条日志记录
     * @Param: 表格的字段封装对象
     * @return: 返回影响行数
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int update(StudentLog studentLog) throws Exception {

        Connection connection = trabAOP.getThreadLocal().get();

        String sql = "UPDATE student SET sid=?,sname=?,age=?,sex=?,address=?,operation_type=?,log_time=? WHERE log_id=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, studentLog.getSid());
        preparedStatement.setString(2, studentLog.getSname());
        preparedStatement.setInt(3, studentLog.getAge());
        preparedStatement.setString(4, studentLog.getSex());
        preparedStatement.setString(5, studentLog.getAddress());
        preparedStatement.setString(6, studentLog.getOperation_type());
        preparedStatement.setDate(7, (Date) studentLog.getLog_time());
        preparedStatement.setInt(8, studentLog.getLog_id());

        return preparedStatement.executeUpdate();
    }
}

6.编写逻辑层的实现类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.zero01.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.zero01.dao.DAO;
import org.zero01.dao.LogDAO;
import org.zero01.pojo.Student;
import org.zero01.pojo.StudentLog;

import java.util.List;

@Component("schoolService")
public class SchoolService implements School {

    @Autowired
    private DAO dao;
    @Autowired
    private LogDAO logDAO;

    /**
     * @Description: 映射两张表格中相同的字段
     * @Author: 01
     * @Date: 2018/3/6
     */
    public StudentLog stuMap(Student student, String operation_type) {

        StudentLog studentLog = new StudentLog();
        if (student != null) {
            studentLog.setSid(student.getSid());
            studentLog.setSname(student.getSname());
            studentLog.setAge(student.getAge());
            studentLog.setSex(student.getSex());
            studentLog.setAddress(student.getAddress());
        }
        studentLog.setOperation_type(operation_type);

        return studentLog;
    }

    /**
     * @Description: 入学
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int enterSchool(Student student) throws Exception {

        int sid = dao.insert(student);
        student.setSid(sid);

        return logDAO.insert(stuMap(student, "add"));
    }

    /**
     * @Description: 删除学生数据
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int deleteStudentData(int sid) throws Exception {
        Student student = dao.selectById(sid);
        if (student != null) {
            student.setSid(sid);
            dao.delete(sid);
        } else {
            return 0;
        }

        return logDAO.insert(stuMap(student, "delete"));
    }

    /**
     * @Description: 搜索某个学生的资料
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public Student searchStudentData(int sid) throws Exception {
        Student student = dao.selectById(sid);
        if (student != null) {
            logDAO.insert(stuMap(student, "selectById"));
        } else {
            return null;
        }
        return student;
    }

    /**
     * @Description: 搜索全部学生的资料
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public List<Student> searchStudentsData() throws Exception {
        List<Student> students = dao.selectAll();
        logDAO.insert(stuMap(null, "selectAll"));
        return students;
    }

    /**
     * @Description: 修改某个学生的资料
     * @Param:
     * @return:
     * @Author: 01
     * @Date: 2018/3/6
     */
    public int alterStudentData(Student studentNew) throws Exception {
        Student studentOld = dao.selectById(studentNew.getSid());
        int row = dao.update(studentNew);
        logDAO.insert(stuMap(studentOld, "alter"));

        return row;
    }
}

经过测试后两张表格的内容如下:

小结:

在以上代码中,我们通过Spring AOP编写了一个切面类,完成了一个简单的事务控制。事务控制与数据库连接对象的开关都交给切面类去完成,这样我们的JDBC代码里就不需要去控制事务了,只需要关注核心的SQL语句即可,也减少了很多重复的代码。

从这个例子里,我们认识到了AOP技术如何应用在事务管理上,也知道了要将一些非核心关注点,但是又很多地方需要使用的功能交给切面去完成,并且需要把切面模块化,这样才能提高切面的复用性。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/03/06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
《Spring实战》读书笔记-第4章 面向切面的Spring
在软件开发中,散布于应用中多的功能被称为横切关注点(cross-cutting concern)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务相分离正是面向切面编程(AOP)所要解决的问题。
Java架构师必看
2020/04/10
6000
面向切面:AOP
大家好,今天本篇博客我们来了解Spring里边的另一个重要部分,叫做AOP,也就是我们说的面向切面编程。
叫我阿杰好了
2023/11/14
2230
面向切面:AOP
Spring AOP面向切面编程
spring提供了一种可插拔的组件技术。听起来很高大上,但在我们日常生活中经常遇到这样的场景,比如说我们现在开发了两个软件模块,A和B,假设软件模块A是系统的用户管理模块,而软件模块B是系统的员工管理模块。这两个模块都拥有自己的业务处理类,他们执行的过程也是以上到下依次执行的。现在我对这两个模块提出一个要求,这两个模块从上到下进行业务处理的过程中,我希望都要进行权限过滤,只有拥有权限的用户才可以访问对应的模块。你可能会在运行实际代码前去增加相应的权限判断的业务代码,A模块加一个,B模块加一个,这样做固然没问题。但是有一天,项目经理说我们现在不需要这两块功能了,那该怎么办呢?此时你又该打开它对应的代码,把所有的权限控制代码全都去掉。那在这时候,有没有更好的办法呢?答案是肯定的。Spring AOP面向切面编程就可以很好地解决这个问题。
害恶细君
2022/11/22
5890
Spring AOP面向切面编程
Spring学习总结(三)——Spring实现AOP的多种方式
AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术。AOP是OOP的补充,是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP可以分为静态织入与动态织入,静态织入即在编译前将需织入内容写入目标模块中,这样成本非常高。动态织入则不需要改变目标模块。Spring框架实现了AOP,使用注解配置完成AOP比使用XML配置要更加方便与直观。上一篇随笔中已经详细讲了代理模式。
张果
2022/05/09
4620
Spring学习总结(三)——Spring实现AOP的多种方式
Spring实战4—面向切面编程主要内容
在南方没有暖气的冬天,太冷了,非常想念北方有暖气的冬天。为了取暖,很多朋友通过空调取暖,但是空调需要耗电,也就需要交不少电费。没家都会有一个电表,每隔一段时间都会有记录员来家里收取这段时间的电费。
阿杜
2018/08/06
1K0
Spring实战4—面向切面编程主要内容
spring aop面向切面原理,用处和实力讲解
上面打印的语句,其实就相当于日志,监控我有没有保存成功,这里我保存的是person对象,如果我还有student,teacher,dog等等很多对象都需要做增删改查操作,是不是在每个增删改查的语句前后都加上这两句话呢?这样不是很繁琐。那么有没有办法让每有执行save操作时就自动前后打印日志呢?这里就应运而生了面向切面AOP
全栈程序员站长
2022/08/09
2370
spring aop面向切面原理,用处和实力讲解
Spring(4)——面向切面编程(AOP模块)
Spring AOP 简介 如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。 AOP 即 Aspect Oriented Program 面向切面编程 首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务 所谓的周边功能,比如性能统计,日志,事务管理等等 周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面 在面向切面编程AO
我没有三颗心脏
2018/04/26
6790
Spring(4)——面向切面编程(AOP模块)
【Spring进阶】基于注解的面向切面编程(AOP)详解
面向切面编程(AOP)是一种编程范式,它允许开发者将横切关注点(如日志记录、事务管理、安全性等)与业务逻辑分离,从而提高代码的模块化和可维护性。在Java中,AOP通常通过使用框架如Spring来实现。
王也518
2024/04/22
1.3K0
Spring核心之面向切面编程AOP
第三参数,实现这个接口InvocationHandler,创建代理对象,写增强的部分
用户9615083
2022/12/25
3800
Spring核心之面向切面编程AOP
Spring-aop面向切面
        1)Advice,通知/增强:类方法中提出来的共性功能(大白话就是提出来的重复代码)         2)Pointcut,切入点/切点:通知返回的方法         3)连接点:方法         4)织入:运行时通知插入到方法的过程         5)aspect,切面:通知功能在什么时候插入到切入点(通知和切入点的结合)         6)目标对象:被增强的对象         7)代理:应用切面的过程(AOP框架使用代理模式创建对象,实现在连接处插入增强)
chao超的搬运文章
2023/10/15
1510
Spring-aop面向切面
Spring AOP:面向切面编程的利器
总之,Spring AOP是一种非常强大的编程技术,它可以帮助我们实现代码的解耦和复用,提高代码的可维护性和可扩展性。
小小程序员
2023/03/19
3860
Spring AOP:面向切面编程的利器
Spring AOP 面向切面编程
先说一下 Spring框架已经继承了,不用我们手动去写 动态代理的实现方式,但是我们也要了解一下
收心
2022/01/17
3440
Spring AOP 面向切面编程
再学习之Spring(面向切面编程).
一、概念 1、理论     把横切关注点和业务逻辑相分离是面向切面编程所要解决的问题。如果要重用通用功能的话,最常见的面向对象技术是继承(inheritance)或 组成(delegation)。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用组成可能需要对委托对象进行复杂的调用。切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。Spring AOP 基于动态代理,所以Spring只支持方法连接点,这与一些其他的AOP框架是不同的,例如AspectJ和JB
JMCui
2018/03/16
7700
再学习之Spring(面向切面编程).
JAVAEE框架整合技术之Spring02-AOP面向切面编程技术
@PropertySouce是spring3.1开始引入的基于java config的注解。
张哥编程
2024/12/13
1270
JAVAEE框架整合技术之Spring02-AOP面向切面编程技术
Spring使用注解配置依赖注入
大部分情况下,使用Spring配置依赖注入时,都是使用注解来进行配置,因为注解比xml要方便和简单。不过类似于数据源对象这种配置信息容易变更的对象除外,这种对象使用xml文件来进行配置会更适合,方便于在外部进行修改,而不需要打开代码来进行修改。
端碗吹水
2020/09/23
9650
什么是AOP面向切面编程?怎么简单理解?
面向切面编程(AOP)通过将横切关注点(cross-cutting concerns)分离出来,提供了一种增强代码模块化和可维护性的方法。
张飞的猪
2024/11/06
1270
什么是AOP面向切面编程?怎么简单理解?
Spring对JDBC的模板支持——JdbcTemplate
Spring的JdbcTemplate是一个对JDBC的模板封装,它提供了一套JDBC的模板,能让我们写持久层代码时减少多余的代码,简化JDBC代码,使代码看起来更简洁。在介绍Spring的JdbcTemplate使用方法之前我们先来讨论一个问题,以下这是一段常见的往数据库写入数据的JDBC代码:
端碗吹水
2020/09/23
6820
Spring AOP(面向切面编程)
Spring AOP 可以劫持一个执行的方法,在方法执行之前或之后添加额外的功能。通常情况下,AOP把项目中需要在多处用到的功能,比如日志、安全和事务等集中到一个类中处理,而不用在每个需要用到该功能的地方显式调用。
SuperHeroes
2019/03/12
6450
Spring 框架学习(六)面向切面编程 AOP
在软件开发中散布于应用中多处的功能被称为横切关注点(crossing-cutting concern)。通常这也横切关注点一般是与业务逻辑相分离的。而面向切面编程将会解决如何将横切关注点与与业务逻辑分离的问题。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nRIEcgxi-1571404087194)(en-resource://database/9500:1)] 横切关注点往往是影响应用多处的功能
求和小熊猫
2020/11/25
3560
面向切面的Spring
  本文是博主在看完面向切面的Spring(《Spring实战》第4章)后的一些实践笔记。   为什么要用AOP呢?作者在书中也明确提到了,使用AOP,可以让代码逻辑更多的去关注自己本身的业务,而不用混杂和关注一些其它的东西。包括:安全,缓存,事务,日志等等。
happyJared
2018/09/20
4480
面向切面的Spring
相关推荐
《Spring实战》读书笔记-第4章 面向切面的Spring
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档