Spring面向切面编程

  • ​🤪个人主页i笨笨i
  • 🐽版权:本文由【i笨笨i】原创,需要转载联系博主
  • 🎉欢迎关注、点赞、收藏(一键三连)和订阅专栏哦!

一、Spring面向切面编程

1.1、为什么使用切面编程思想

假设一个业务是用于员工工资统计,这个业务已经写好了但又提出了这些数据必须记入数据库,此时难道又要重新修改源代码吗?过几天领导又说不记入数据库了,这是又该怎么办?
​ 出现这些问题的原因在于:反复改需求,而且这些需要跟我们统计业务逻辑关系不大,同时不单单是员工工资还有奖金等,如果都写一遍的话系统无疑变得更加臃肿,而且不易维护,想到这里,你可能会想将这些逻辑关系不强的代码(公共代码)拿出来,不放在业务代码中,这样利用维护。但是如何在不改变源代码的情况下,而且还将功能加入进去。

​ 解决方法:采用面向切面编程的方式。在执行原方法前(或后)执行公共代码以达到不修改源代码还能完成相关需求。

1.2、AOP概念

​ 面向切面编程(Aspect-oriented Programming 简称AOPAOP) ,是相对面向对象编程(Object-oriented Programming 简称OOP)的框架,作为OOP的一种功能补充. OOP主要的模块单元是类(class)。而AOP则是切面(aspect)。切面会将诸如事务管理这样跨越多个类型和对象的关注点模块化(在AOP的语义中,这类关注点被称为横切关注点(crosscutting))。

AOP在Spring Framework中用于:

  • 提供声明式企业服务,特别是用于替代EJB的声明式服务。最重要的服务是声明式事务管理(declarative transaction management),这个服务建立在Spring的抽象事务管理(transaction abstraction)之上。
  • 允许开发者实现自定义切面,使用AOP来完善OOP的功能。
  • Spring框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。

​ 让我们从定义一些核心AOP概念和术语开始。 这些术语不是特定于Spring的。

  • 切面(Aspect):指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@AspectJ注解方式)来实现。
  • 连接点(Join point):在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
  • 通知(Advice):在切面的某个特定的连接点上执行的动作。通知有多个类型,包括“around”,“before”,“after”等等。许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
  • 切点(Pointcut):匹配连接的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如:当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点定义。
  • 引入(Introduction):声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使beanIsModified接口,以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。
  • 目标对象(Target object):被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK代理或CGLIB代理。
  • 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被 被通知的对象的过程,这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。Spring和其他纯Java AOP框架一样,是运行时完成织入的。

Spring AOP包含以下类型通知:

  • 前置通知(Before advice):在连接点之前运行但无法阻止执行流程进入连接点的通知(除非发生异常)。
  • 后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如:当方法没有抛出任何异常并正常返回时执行)。
  • 后置异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
  • 后置通知(总会执行)(After (finally) advice):当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
  • 环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义行为。他可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。

环绕通知是最常用的一种通知类型。与AspectJ一样,在选择Spring提供的通知类型时,团队推荐开发者尽量使用简单的通知类型来实现需要的功能。

1.2.1、解释含义

​ 在BookDaoImpl类中找到update方法想要在输出语句前执行公共代码部分,图中的1表示找到的切点,但是公共代码写在其他类的方法中,怎样将二者连接起来,通过切面将二者连接起来

​ 在通知类中定义了Pointcut(切点),也定义了需要执行的公共代码部分(before方法),二者是通过@Before("pt()")这样连接起来的。必须准确找到切点但Java要依靠类中方法所以定义了一个空的方法,使用@Before注解此注解称为通知类型注解,Pointcut内的表达式称为切点表达式用于找到切点。

1.3、Spring AOP的功能和目标

​ Spring AOP是用纯Java实现的。 不需要特殊的编译过程。 Spring AOP不需要控制类加载器层次结构,因此适合在servlet容器或应用程序服务器中使用。

​ Spring框架的一个核心原则是非侵入性。这意味着开发者无需在自身的业务/域模型上被迫引入框架特定的类和接口。然而,有些时候,Spring框架可以让开发者选择引入Spring框架特定的依赖关系到业务代码。 给予这种选择的理由是因为在某些情况下它可能是更易读或易于编写某些特定功能。

1.4、AOP代理

​ Spring默认使用标准的JDK动态代理来作为AOP的代理。这样任何接口(或者接口的set)都可以被代理。Spring也支持使用CGLIB代理。对于需要代理类而不是代理接口的时候CGLIB代理是很有必要的。如果业务对象并没有实现接口,默认就会使用CGLIB代理 。

1.5、AOP入门案例

此案例使用注解的方式,也可以用XML方式实现。

1.5.1、导入pom.xml坐标

注:由于spring-context坐标依赖spring-aop坐标。

<!-- Spring  Framework-->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.2.10.RELEASE</version>
</dependency>

<!-- 切面相关坐标 -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>5.2.6.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.4</version>
</dependency>

<!-- 测试框架 -->
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.13</version>
	<scope>test</scope>
</dependency>

要求:在一个方法前输出一句话,但不能修改源代码,这时我们想到面向切面编程。

1.5.2、实现过程

  • 定义dao接口与实现类
package com.item.dao;
public interface BookDao {
    void save();
    void update();
}

package com.item.dao.impl;
@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save ...");
    }

    @Override
    public void update() {
        
        System.out.println("book dao update ...");
        
    }
}
  • 通过了解AOP概念,此时应该定义通知类,制作通知
public class MyAdvice {
    public void before(){
        System.out.println("before ...");
    }
}
  • 通知定义好后,要匹配到切点上所以定义切点
public class MyAdvice {

    @Pointcut("execution(void com.item.dao.BookDao.update())")
    private void pt(){}

    public void before(){
        System.out.println("before ...");
    }
}
  • 通知和切点都定义好了,绑定二者并指定通知添加到原始连接点的具体执行位置
public class MyAdvice {

    @Pointcut("execution(void com.item.dao.BookDao.update())")
    private void pt(){}

    //@Before表示方法执行前执行通知
    @Before("pt()")
    public void before(){
        System.out.println("before ...");
    }
}
  • 定义通知类受Spring容器管理,并定义当前类为切面类

在MyAdvice类前加上@Component@Aspect注解,@Aspect将此类注册为切面类,而且此类也要注册到ioc容器中进行管理。

  • 开启Spring对AOP注解的支持
@Configuration
@ComponentScan("com.item")
//开启Spring对AOP注解的支持
@EnableAspectJAutoProxy
public class SpringConfig {
}
  • 测试
@Test
public void test(){
	ApplicationContext ioc =
        		new AnnotationConfigApplicationContext(SpringConfig.class);
	BookDao bean = ioc.getBean(BookDao.class);
	bean.update();
}

此时你可以看到执行update方法前输出了before …

1.5.3、总结

  1. 导入坐标pom.xml
  2. 制作连接点方法(原始操作,Dao接口和实现类)
  3. 制作共性功能(通知类与通知)
  4. 定义切入点
  5. 绑定切入点与通知关系(切面)
  6. 开启spring对aop的支持

1.6、基于XML的AOP

1.6.1、入门案例XML化

​ 我们将AOP入门案例改造成基于XML配置的AOP。

  • 导入坐标pom.xml(与入门案例相同)
  • 定义dao接口与实现类(与入门案例相同)
  • 定义通知类(切面)

此时通知类与入门案例的通知略微差异。

public class MyAdvice {
    public void before(){
        System.out.println("before ...");
    }
}
  • 在主配置文件中配置AOP
<?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: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/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 1.注册BookDaoImpl的Bean -->
<bean id="bookDao" class="com.item.dao.impl.BookDaoImpl"></bean>

<!-- 2.以下操作基于XML的AOP配置步骤
    2.1 把 通知/增强 Bean也需要注册到Spring容器中
    2.2 使用<aop:config>标签表示开始配置AOP
    2.3 使用<aop:aspect>标签表示开始配置切面
        id属性:给切面唯一标识
        ref属性:指定通知类bean的id
    2.4 在<aop:aspect>标签内部使用通知类型标签来配置通知
        例:<aop:before>的
            method属性:指定MyAdvice类中哪个方法是前置通知
            pointcut属性:用于指定切点表达式,用于指定哪些方法需要增强
-->

<!-- 2.1 把 通知/增强 Bean也需要注册到Spring容器中 -->
<bean id="myAdvice" class="com.item.advice.MyAdvice"></bean>

<!-- 2.2 配置AOP 启用aop命名空间-->
<aop:config>
       <!-- 2.3 配置切面 -->
	<aop:aspect id="advice" ref="myAdvice">
             <!-- 2.4 配置通知的类型,并且建立通知方法和切入点方法的联系 -->
		<aop:before 
                    method="before" 
                    pointcut="execution(void com.item.dao.BookDao.update())"/>
	</aop:aspect>
</aop:config>

</beans>
  • 测试
@Test
public void test(){
	ApplicationContext ioc = 
        			new ClassPathXmlApplicationContext("classpath:/spring.xml");
	BookDao bean = ioc.getBean(BookDao.class);
	bean.update();
}

1.6.2、AOP通知类型

​ AOP通知类型描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置。在AOP概念中了解到AOP有5种通知类型:前置通知、返回通知、异常通知、后置通知(最终通知)、环绕通知。

​ 在以下案例中,使用入门案例的dao接口以及实现类,只是将MyAdvice修改如下:

public class MyAdvice {
    //前置通知
    public void before(){
        System.out.println("before ...");
    }
    //返回通知
    public void after(){
        System.out.println("after ...");
    }
    //异常通知
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
    //后置通知(最终通知)
    public void afterReturning(){
        System.out.println("afterReturning ...");
    }
    //环绕通知
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        pjp.proceed();
        System.out.println("around after advice ...");
    }
}
1.6.2.1、前置通知

将需要增强的类和通知注册到同一个IOC容器中。

使用<aop:config>标签配置AOP,<aop:aspect>标签配置切面,<aop:before>标签配置前置通知。

method属性:指定MyAdvice类中哪个方法是前置通知。

pointcut属性:用于指定切点表达式,用于指定哪些方法需要增强

<bean id="bookDao" class="com.item.dao.impl.BookDaoImpl"></bean>
<bean id="myAdvice" class="com.item.advice.MyAdvice"></bean>
<!-- 配置AOP -->
<aop:config>
        <!-- 配置切面 -->
	<aop:aspect id="advice" ref="myAdvice">
             <!-- 配置通知的类型,并且建立通知方法和切入点方法的联系 -->
		<aop:before 
                    method="before" 
                    pointcut="execution(void com.item.dao.BookDao.update())"/>
	</aop:aspect>
</aop:config>
1.6.2.2、返回通知

使用<aop:after >标签完成通知和切点之间的绑定。

method属性:指定MyAdvice类中哪个方法是前置通知。

pointcut属性:用于指定切点表达式,用于指定哪些方法需要增强

<!-- 配置AOP -->
<aop:config>
       <!-- 配置切面 -->
	<aop:aspect id="advice" ref="myAdvice">
             <!-- 配置通知的类型,并且建立通知方法和切入点方法的联系 -->
		<aop:after 
                   method="after" 
                   pointcut="execution(void com.item.dao.BookDao.update())"/>
	</aop:aspect>
</aop:config>
1.6.2.3、异常通知

在原方法中,如果有报错且有异常通知,就会调用异常通知。使用<aop:after-throwing >标签。

<!-- 配置AOP -->
<aop:config>
        <!-- 配置切面 -->
	<aop:aspect id="advice" ref="myAdvice">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的联系 -->
		<aop:after-throwing 
                            method="afterThrowing" 
                            pointcut="execution(void com.item.dao.BookDao.update())"/>
	</aop:aspect>
</aop:config>
1.6.2.4、后置通知(最终通知)

执行时间:方法执行完毕后,才会执行此通知。返回通知早于后置通知执行。使用<aop:after-returning >标签。

<!-- 配置AOP -->
<aop:config>
	<!-- 2配置切面 -->
	<aop:aspect id="advice" ref="myAdvice">
		<!-- 2配置通知的类型,并且建立通知方法和切入点方法的联系 -->
             <aop:after-returning 
                                method="afterReturning" 	
                                pointcut="execution(voidcom.item.dao.BookDao.update())"/>
	</aop:aspect>
</aop:config>
1.6.2.5、环绕通知

以下是环绕通知方法:

​ ProceedingJoinPoint参数并且还抛出Throwable异常,不使用ProceedingJoinPoint接口就无法调用原始操作(被增强的方法),又因为无法确定方法里有无异常所以抛出异常。

​ pjp.proceed();语句表示对原始操作的调用(被增强的方法)。这里代理机制。

public void around(ProceedingJoinPoint pjp) throws Throwable {
	System.out.println("around before advice ...");
	pjp.proceed();//此语句表示对原始操作的调用
       System.out.println("around after advice ...");
}

1.7、基于注解的AOP

1.7.1、导入坐标pom.xml

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.2.10.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>5.2.6.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.4</version>
</dependency>

1.7.2、使用Java配置启用@AspectJ支持

​ 要在Spring配置中使用@AspectJ切面,需要启用Spring支持,用于根据@AspectJ切面配置Spring AOP,并根据这些切面自动代理bean(事先判断是否在通知的范围内)。 通过自动代理的意思是:如果Spring确定一个bean是由一个或多个切面处理的,将据此为bean自动生成代理bean,并以拦截方法调用并确保需要执行的通知。

可以使用XML或Java配置的方式启用@AspectJ支持。不管哪一种方式,您还需要确保AspectJ的aspectjweaver.jar库位于应用程序的类路径中(版本1.8或更高版本)。此库可在AspectJ分发的lib 目录中或Maven Central存储库中找到。

​ 要使用Java @Configuration启用@AspectJ(此注解添加在通知类上)支持,请添加 @EnableAspectJAutoProxy注解,

@Configuration
@EnableAspectJAutoProxy
public class SpringConfig {
}

1.7.3、声明切面

​ 启用了@AspectJ支持后,在应用程序上下文中定义的任意bean(有@Aspect注解)的类都将被Spring自动检测,并用于配置Spring AOP。

​ 切面(使用 @Aspect的类)可以拥有方法和属性,与其他类并无不同。也可以包括切点、通知和内置类型(即引入)声明。

​ 在Spring AOP中,不可能将切面本身被作为其他切面的目标。类上的@Aspect注解表明他是一个切面并且排除在自动代理的范围之外。

​ 添加了@Aspect注解后,扫描器才会知道这个类是切面。添加@Component为了使IOC容器管理此类。

@Component
@Aspect
public class MyAdvice {
}

1.7.4、声明切点

​ 切点决定了匹配的连接点,从而使我们能够控制通知何时执行。Spring AOP只支持使用Spring bean的方法执行连接点(通知和被增强的类都要注册到IOC容器中),所以可以将切点看出是匹配Spring bean上方法的执行。

​ 切点的声明包含两个部分:包含名称和任意参数的签名,以及明确需要匹配的方式执行的切点表达式。

​ 在@AspectJ注解方式的AOP中,一个切点的签名由常规方法定义来提供, 并且切点表达式使用 @Pointcut注解指定(方法作为切点签名必须有类型为void的返回)。

1.7.4.1、支持切点标识符

详情可以看切点表达式小节。

Spring AOP支持使用以下AspectJ切点标识符(PCD),用于切点表达式:

  • execution: 用于匹配方法执行连接点。 这是使用Spring AOP时使用的主要切点标识符。
  • within: 限制匹配特定类型中的连接点(在使用Spring AOP时,只需执行在匹配类型中声明的方法)。
  • this: 在bean引用(Spring AOP代理)是给定类型的实例的情况下,限制匹配连接点(使用Spring AOP时方法的执行)。
  • target: 限制匹配到连接点(使用Spring AOP时方法的执行),其中目标对象(正在代理的应用程序对象)是给定类型的实例。
  • args: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中变量是给定类型的实例。 AOP) where the arguments are instances of the given types.
  • @target: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注解。
  • @args: 限制匹配连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注解。
  • @within: 限制与具有给定注解的类型中的连接点匹配(使用Spring AOP时在具有给定注解的类型中声明的方法的执行)。
  • @annotation:限制匹配连接点(在Spring AOP中执行的方法具有给定的注解)。

1.7.5、声明一个通知

​ 通知是与切点表达式相关联的概念,可以在切点匹配的方法之前、之后或之间执行。切点表达式可以是对命名切点的简单引用,也可以是即时声明的切点表达式。

1.7.5.1、前置通知

名称:@Before

类型:方法注解

位置:通知方法定义上方

作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行

  • 范例:
public class MyAdvice {
    @Before("execution(void com.item.dao.BookDao.update())")
    public void before(){
        System.out.println("before ...");
    }
}

//也可以这样书写
public class MyAdvice {

    //复用性强
    @Pointcut("execution(void com.item.dao.BookDao.update())")
    private void pt(){}

    @Before(value = "pt()")//@Before(pt()")
    public void before(){
        System.out.println("before ...");
    }
}

相关属性:value(默认):切入点表达式。(可省略)

@Before(value = “execution(void com.item.dao.BookDao.update())”)/

1.7.5.2、返回通知

名称:@AfterReturning

类型:方法注解

位置:通知方法定义上方

作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行

  • 范例
public class MyAdvice {
    @AfterReturning("execution(void com.item.dao.BookDao.update())")
    public void afterReturning(){
        System.out.println("afterReturning ...");
    }
}

相关属性:value(默认):切入点表达式。(可省略)

1.7.5.3、异常通知

名称:@AfterThrowing

类型:方法注解

位置:通知方法定义上方

作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后运行

  • 范例
@AfterThrowing("execution(void com.item.dao.BookDao.update())")
public void afterThrowing(){
	System.out.println("afterThrowing");
}

相关属性:value(默认):切入点表达式。(可省略)

1.7.5.4、后置通知(最终通知)

名称:@After

类型:方法注解

位置:通知方法定义上方

作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行

  • 范例
public class MyAdvice {
	@After("execution(void com.item.dao.BookDao.update())")
	public void after(){
        System.out.println("after ...");
	}
}

相关属性:value(默认):切入点表达式。(可省略)

1.7.5.5、环绕通知

名称:@Around

类型:方法注解

位置:通知方法定义上方

作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行

  • 范例1:(无返回值)
//被增强方法
public void update() {
	System.out.println("book dao update ...");
}

//通知
@Around("execution(void com.item.dao.BookDao.update())")
public void around(ProceedingJoinPoint pjp) throws Throwable {
	System.out.println("around before advice ...");
	pjp.proceed();//此语句表示调用原始方法
	System.out.println("around after advice ...");
}

如果未调用pjp.proceed();语句,则不会执行原方法内容。

  • 范例2:(有返回值)
//被增强方法
public int save() {
       System.out.println("book dao save ...");
	return 1;
}

//通知
@Around("execution(int com.item.dao.BookDao.save())")
public Object aroundSave(ProceedingJoinPoint pjp) throws Throwable {
	System.out.println("around before advice ...");
	Integer ret = (Integer)pjp.proceed();
	System.out.println("around after advice ...");
	return ret;
}


//测试
@Test
public void test(){
	BookDao bean = ioc.getBean(BookDao.class);
       int save = bean.save();
       System.out.println(save);
}

​ public Object 方法名(ProceedingJoinPoint pjp) throws Throwable { … },因为不知道原方法返回什么,所以写Object类型,pjp.proceed()语句可以获取到返回值,如果原方法是void则返回null;

  • 注意事项
  1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知。
  2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行(隔离)。
  3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型。
  4. 原始方法的返回值,如果是void类型通知方法的返回值类型可以设置成void,也可设置成Object。
  5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象。
1.7.5.6、通知的参数

获取切入点方法的参数:

  • JoinPoint:适用于前置、返回,后置、抛出异常后通知。
  • ProceedingJoinPoint:适用于环绕通知。

获取切入点返回值:

  • 后置通知
  • 环绕通知

获取切入点方法运行异常信息:

  • 异常通知
  • 环绕通知
  • getArgs(): 返回方法参数.
  • getThis(): 返回代理对象.
  • getTarget(): 返回目标对象.
  • getSignature():返回正在通知的方法的描述.
  • toString(): 打印方法被通知的有用描述.
  • JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数。
@Before("execution(void com.item.dao.BookDao.update())")
public void before(JoinPoint jp){
	Object[]  args = jp.getArgs();
	System.out.println("before ...");
}
  • ProceedingJoinPoint是JoinPoint的子类。
@Around("execution(int com.item.dao.BookDao.save())")
public Object aroundSave(ProceedingJoinPoint pjp) throws Throwable {
	Object[]  args = jp.getArgs();
	System.out.println(Arrays.toString(args));
	args[0] = 666;
	Object ret = pjp.proceed();
	System.out.println("around after advice ...");
	return ret;
}

利用getArgs方法获取到原方法的方法参数,此时在通知内可以修改传入的参数,例如:args[0] = 666;。

  • 抛出异常后,通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象。

如果有JoinPoint形参必须是第一个参数。

@AfterReturning(
	value = "execution(void com.item.dao.BookDao.update())",
	returning = "ret")
public void afterReturning(Object ret){
	System.out.println("afterReturning ...");
}
  • 抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象。

throwing属性:此属性值是接收异常信息对象(来自形参)。如果有JoinPoint形参必须是第一个参数。

@AfterThrowing(
	value = "execution(void com.item.dao.BookDao.update())",
	throwing = "throwable")
public void afterThrowing(Throwable throwable){
	System.out.println("afterThrowing");
}
  • 抛出异常后,通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象。
@Around("execution(int com.item.dao.BookDao.save())")
public Object around(ProceedingJoinPoint pjp) {
	Object ret = null;
	try {
	     ret = pjp.proceed();
	}catch (Throwable throwable){
            throwable.printStackTrace();
	}
	return ret;
}

1.8、切点表达式

  • 切入点:要进行增强的方法
  • 切入点表达式:要进行增强的方法的描述方式
    • 描述一:执行com.item.dao包下的BookDao接口中的无参数update方法(**描述接口,**推荐)
      • execution(void com.item.dao.BookDao.update())
    • 描述二:执行com.item.dao包下的BookDao接口中的无参数update方法(描述到具体类
      • execution(void com.item.dao.impl.BookDaoImpl.update())

1.8.1、切入点表达式标准格式

动作关键字 (访问修饰符 返回值 包名.类/接口.方法名(参数) 异常名)

  • 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点。
  • 访问修饰符:public等。可省略
  • 返回值
  • 类/接口名
  • 方法名
  • 参数
  • 异常名:方法定义中抛出指定异常,可以省略

例:execution(void com.item.dao.impl.BookDaoImpl.update()),此切点表达式只切入了一个方法,如果要切入多个方法要使用什么?

使用通配符描述切入点,快速描述。

  • *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

    • 例:execution(public * com.item.*.BookService.find==(==))
    • 匹配com.item包下的BookService类或接口中所以find开头的带有一个参数的方法。
  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写。

    • 例:execution(public Book com==…BookService.findById(…==))
    • 匹配com包下的任意包中的BookService类或接口中所以名称为findById的方法
  • +专用于匹配子类类型

    • 例:execution(* **Service+.*(…))
    • 匹配任意包下以Service结尾的类或接口的子类任意方法

1.8.2、切入表达式的类型

支持切点标识符

Spring AOP支持使用以下AspectJ切点标识符(PCD),用于切点表达式:

  • execution:用于匹配方法执行连接点。这是使用Spring AOP时使用的主要切入点标识符。
  • within:限制匹配特定类型中的连接点(在使用Spring AOP时,只需执行在匹配类型中的声明的方法)。
  • this:在bean引用(Spring AOP代理)是给定类型的实例的情况下,限制匹配连接点(使用Spring AOP时方法的执行)。
  • target:限制匹配到连接点(使用Spring AOP时方法的执行),其中目标对象(正在代理的应用程序对象)是给定类型的实例。
  • args:限制与连接点的匹配(使用Spring AOP时方法的执行),其中变量是给定类型的实例。AOP)where the arguments are instances of the given types。
  • @target:限制与连接点的匹配(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注解。
  • @args:限制匹配连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注解。
  • @within:限制与具有给定注解的类型中的连接点匹配(使用Spring AOP时在具有给定注解的类型中声明的方法的执行)。
  • @annotation:限制匹配连接点(在Spring AOP 中执行的方法具有给定的注解)。

详解

  • within表达式详解

通过类名进行匹配,粗粒度的切入点表达式。

within(包名.类名),则这个类中的所有的连接点都会被表达式识别,称为切入点。

//在通知类中
@Before("within(com.item.service.impl.UserServiceImpl)")
public void with(){
	System.out.println("with");
}
//测试,且读取了配置信息
UserService bean = ctx.getBean(UserService.class);
bean.delete(1);

在within表达式中可以使用*号匹配符,匹配指定包下所有的的类。注意只匹配当前包,不包括当前包的子孙包。

@Before("within(com.item.service.impl.*)")
public void with(){
	System.out.println("with");
}

1.8.3、合并切点表达式

可以使用 &&, ||!等符号进行合并操作。也可以通过名字来指向切点表达式。 以下示例显示了三个切入点表达式:

@Pointcut("execution(* com.item.dao.*.*Dao(..) && 
                    * com.item.service.*.*Service(..))")
private void pt1() {}

@Pointcut("execution(* com.item.dao.*.*Dao(..) ||
                    * com.item.service.*.*Service(..))")
private void pt2() {} 

@Pointcut("! execution(* com.item.dao.*.*Dao(..) ")
private void pt3() {} 

1.8.4、书写技巧

  • 所有代码按照标准规范开发,否则以下技巧全部失效。
  • 描述切入点通常描述接口,而不描述实现类。
  • 访问控制修饰符针对接口开发均采用public描述(可以省略访问控制修饰符)。
  • 返回值类型对于增删改类使用精准类型加上匹配,对于查询类使用*通配符快速描述。
  • 包名书写尽量不使用..匹配效率过低,常用*做单个包描述匹配或精准匹配。
  • 接口名/类名 书写名称与模块相关的采用*匹配,例如:BookService书写成*Service绑定业务层接口名。
  • 方法名书写以动词进行精准匹配,名词采用*匹配。例如:getById写成getBy*,selectAll写成selectAll。
  • 参数规则较为复杂,根据业务灵活调整。
  • 通常不使用异常作为匹配规则。

1.9、AOP工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点(不读取只定义切入点而不与通知相关联)
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
    • 匹配失败,创建对象
    • 匹配成功,创建原始对象(目标对象)de 代理对象
  4. 获取bean执行方法
    • (匹配失败)获取bean,调用方法并执行,完成操作
    • (匹配成功)获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值