谈谈Spring AOP中@Aspect的高级用法示例

前言

本文主要跟大家分享介绍了关于Spring AOP中@Aspect的高级用法,下面话不多说了,来随着小编一起看看详细的介绍吧。

1 切点复合运算

支持在切点定义中加入以下运算符进行复合运算:

运算符 说明
&& 与运算。
! 非运算。
|| 或运算。

2 切点命名

一般情况下,切点是直接声明在需要增强方法处,这种切点的声明方式称为匿名切点,匿名切点只能在声明处被使用 。 如果希望在其它地方可以重用这个切点,我们可以通过 @Pointcut 注解及切面类方法来命名它。

public class NamePointcut {

 /**
 * 切点被命名为 method1,且该切点只能在本类中使用
 */
 @Pointcut("within(net.deniro.spring4.aspectj.*)")
 private void method1() {
 }

 /**
 * 切点被命名为 method2,且该切点可以在本类或子孙类中使用
 */
 @Pointcut("within(net.deniro.spring4.aspectj.*)")
 protected void method2() {
 }

 /**
 * 切点被命名为 method3,且该切点可以在任何类中使用
 * 这里还使用了复合运算
 */
 @Pointcut("method1() && method2()")
 public void method3() {
 }
}

命名切点的结构如下:

切点可访问性修饰符与类可访问性修饰符的功能是相同的,它可以决定定义的切点可以在哪些类中可使用。

因为命名切点仅利用方法名及访问修饰符的信息,所以我们一般定义方法的返回类型为 void ,并且方法体为空 。

定义好切点后,就可以在切面类中引用啦:

@Aspect
public class NamePointcutAspect {

 @After("NamePointcut.method2()")
 public void aspectMethod1() {
 }

 /**
 * 这里使用了复合运算
 */
 @After("NamePointcut.method2() && NamePointcut.method3()")
 public void aspectMethod2() {
 }
}

3 织入顺序

一个连接点可以同时匹配多个切点,而切点所对应的增强在连接点上织入顺序的规则是这样的:

1.如果在同一个切面类中声明的增强,则按照增强在切面类中定义的顺序进行织入;

2.如果增强位于不同的切面类中,并且这些切面类都实现了org.springframework.core.Ordered 接口,则由 Ordered 方法的顺序号决定(顺序号小的先织入);

3.如果增强位于不同的切面类中,但这些切面类没有实现org.springframework.core.Ordered 接口,织入的顺序是不确定的 。

假设有两个切面类 A 与 B,它们都实现了 Ordered 接口,A 的顺序号为 1,B 的顺序号为 2,切面类 A 与 B 都定义了 3 个增强,那么同时匹配这 6 个增强的织入顺序如下图所示:

4 获取连接点信息

4.1 JoinPoint

org.aspectj.lang.JoinPoint 接口表示目标类连接点对象,它定义这些主要方法。

方法 说明
Object[] getArgs() 获取连接点方法运行时的入参列表。
Signature getSignature() 获取连接点的方法签名对象。
Object getTarget() 获取连接点所在的目标对象。
Object getThis() 获取代理对象。

4.2 ProceedingJoinPoint

org.aspectj.lang.ProceedingJoinPoint 继承了 JoinPoint 接口,它新增了两个方法(它们用于执行连接点方法)。

方法 说明
Object proceed() throws Throwable 通过反射执行目标对象连接点处的方法。
Object proceed(Object[] var1) throws Throwable 使用新的入参(替换掉原来的入参),通过反射执行目标对象连接点处的方法。

4.3 示例

Cook 接口:

public interface Cook {

 /**
 * 制作食品
 */
 void make();

 /**
 * 制作
 *
 * @param name 食品名称
 */
 void make(String name);
}

CookA 类:

public class CookA implements Cook {
 public void make() {
 System.out.println("制作食品");
 }

 public void make(String name) {
 System.out.println("制作" + name);
 }
}

在切面类中访问连接点信息:

@Aspect
public class JoinPointAspect {

 @Around("within(net.deniro.spring4.aspectj.CookA)")
 public void test(ProceedingJoinPoint pjp) throws Throwable {
 System.out.println("---------获取连接点对象【开始】---------");
 System.out.println("参数:" + pjp.getArgs()[0]);
 System.out.println("签名对象:" + pjp.getTarget().getClass());

 //执行目标对象方法
 pjp.proceed();
 System.out.println("---------获取连接点对象【结束】---------");

 }
}

Spring bean 配置:

<?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-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

 <!--aspectj 驱动器 -->
 <aop:aspectj-autoproxy/>

 <bean id="cookA" class="net.deniro.spring4.aspectj.CookA"/>
 <bean class="net.deniro.spring4.aspectj.JoinPointAspect"/>
</beans>

输出结果:

---------获取连接点对象【开始】---------
参数:寿司
签名对象:class net.deniro.spring4.aspectj.CookA
制作寿司
---------获取连接点对象【结束】---------

5 绑定连接点的方法入参

args()、this()、target()、@args()、@within()、@target() 和 @annotation() 这些切点函数除可以指定类名外,还可以指定参数名,将目标对象连接点上的方法入参绑定到增强的方法中 。 其中 args() 用于绑定连接点方法的入参, @annotation() 用于绑定连接点方法的注解对象,而 @args() 用于绑定连接点方法入参的注解。

CookC 类:

public class CookC implements Cook {
 public void make() {
 System.out.println("制作食品");
 }

 public void make(String name) {
 System.out.println("制作" + name);
 }

 public void make(String name, int num) {
 System.out.println("制作" + name + " " + num + " 个");
 }
}

切面类:

@Aspect
public class ParamsAspect {

 @Before("target(net.deniro.spring4.aspectj.CookC) && args(name,num,..)")
 public void test(String name,int num) {
 System.out.println("----------绑定连接点入参【开始】----------");
 System.out.println("name:" + name);
 System.out.println("num:" + num);
 System.out.println("----------绑定连接点入参【结束】----------");

 }
}
  • 这里的连接点表达式 args(name,num,..) 会先找到 name 与 num 的类型,从而生成真正的表达式 args(String,int,..)。
  • 增强方法可以通过 name 与 num 得到连接点的方法入参。

切点匹配和参数绑定的过程是这样的:

  1. args()会根据参数名称在增强方法中查到名称相同的入参并获得对应参数的类型,这样就得到了匹配连接点方法的入参类型 。
  2. 连接点方法入参类型所在的位置由参数名在 args() 函数中声明的位置决定 。

上述示例中的匹配过程如下:

Spring 配置:

<!--aspectj 驱动器 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

<bean id="cookC" class="net.deniro.spring4.aspectj.CookC"/>
<bean class="net.deniro.spring4.aspectj.ParamsAspect"/>

注意: 这里必须通过 <aop:aspectj-autoproxy proxy-target-class="true" />来启用 CGLib 动态代理,这是因为 CookC 的 public void make(String name, int num) 是该类独有的方法(非接口定义的方法),所以必须使用 CGLib 生成子类的代理方法 。

单元测试:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
CookC cookC = (CookC) context.getBean("cookC");
cookC.make("寿司", 100);

输出结果:

----------绑定连接点入参【开始】----------
name:寿司
num:100
----------绑定连接点入参【结束】----------
制作寿司 100 个

6 绑定代理对象

使用 this() 或 target() 可绑定被代理对象的实例。通过类实例名绑定对象时,依然具有原来连接点匹配的功能,只是类名是由增强方法中的同名入参类型间接决定的。

@Aspect
public class ProxyAspect {

 @Before("this(cook)")
 public void bind(Cook cook) {
  System.out.println("--------绑定代理对象【开始】--------");
  System.out.println(cook.getClass().getName());
  System.out.println("--------绑定代理对象【结束】--------");
 }
}

首先通过 public void bind(Cook cook) 找出 cook 所对应的类型,接着转换切点表达式为 this(net.deniro.spring4.aspectj.Cook) 。这样就表示该切点匹配所有代理对象为 Cook 类中的所有方法。

输出结果:

--------绑定代理对象【开始】--------
net.deniro.spring4.aspectj.CookC$$EnhancerBySpringCGLIB$$217fb793
--------绑定代理对象【结束】--------

target() 绑定与 this() 相似。

7 绑定类注解对象

@within() 和 @target() 函数都可以将目标类的注解对象绑定到增强方法中。

定义一个日志注解类:

@Retention(RetentionPolicy.RUNTIME)//保留期限
@Target({ElementType.METHOD,ElementType.TYPE})//目标类型
public @interface Log {
 boolean value() default true;//声明成员变量
}

把该注解类应用于 CookD:

@Log
public class CookD implements Cook {
 public void make() {
  System.out.println("制作糕点");
 }

 public void make(String name) {

 }
}

绑定类注解对象:

@Aspect
public class ClassAnnotationObjectAspect {

 @Before("@within(log)")
 public void bind(Log log){
  System.out.println("----------绑定类注解对象【开始】----------");
  System.out.println(log.getClass().getName());
  System.out.println("----------绑定类注解对象【结束】----------");
 }
}

Spring 配置:

<!--aspectj 驱动器 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

<bean id="cookD" class="net.deniro.spring4.aspectj.CookD"/>
<bean class="net.deniro.spring4.aspectj.ClassAnnotationObjectAspect"/>

单元测试:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
CookD cook = (CookD) context.getBean("cookD");
cook.make();

输出结果:

----------绑定类注解对象【开始】----------
com.sun.proxy.$Proxy8
----------绑定类注解对象【结束】----------

从输出结果  com.sun.proxy.$Proxy8 可以看出 ,CookD 类的注解 Log 对象也被代理咯O(∩_∩)O哈哈~

8 绑定返回值

在后置增强中,可以通过 returning 来绑定连接点方法的返回值。

切面:

@Aspect
public class ReturnValueAspect {

 @AfterReturning(value = "target(net.deniro.spring4.aspectj.CookA)", returning = "value")
 public void bind(boolean value) {
  System.out.println("绑定返回值【开始】");
  System.out.println("value:" + value);
  System.out.println("绑定返回值【结束】");
 }
}

注意:returning 的值必须与方法参数名相同。

CookA 新增 smell 方法:

public boolean smell(String name) {
 System.out.println(name + "香吗?");
 return true;
}

单元测试:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
CookA cook = (CookA) context.getBean("cookA");
cook.smell("烤鸭");

输出结果:

烤鸭香吗?
绑定返回值【开始】
value:true
绑定返回值【结束】

9 绑定异常

可以使用 AfterThrowing 注解的 throwing 成员变量来绑定连接点抛出的异常。

切面类:

@Aspect
public class ExceptionAspect {

 @AfterThrowing(value = "target(net.deniro.spring4.aspectj.CookA)",throwing = "e")
 public void bind(CookException e){
  System.out.println("绑定异常【开始】");
  System.out.println("e:" + e.getMessage());
  System.out.println("绑定异常【结束】");
 }
}

注意:throwing 的值必须与方法参数名相同。

单元测试:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
CookA cook = (CookA) context.getBean("cookA");
cook.make("");

输出结果:

绑定异常【开始】
e:煮啥呢???
绑定异常【结束】

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • SpringBoot AOP控制Redis自动缓存和更新的示例

    导入redis的jar包 <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.0.4.RELEASE</version> </dependency> 编写自定义缓存注解 /**

  • Spring的AOP极简入门

    AOP是Spring中的面向切面的编程,这里简单感受一下如何在xml文件中配置一个切面. 如上图所示,配置一个切面的主要思路有以下几个步骤. 1,首先需要把实现切面功能的类声明为一个bean,例如图中的minstrel. 2,前面的配置都在<aop:config>标签下进行.一个切面对应一个<aop:aspect>标签,标签的ref可以指定实现该切面的bean是哪一个. 3,然后定义切入点,使用标签<aop:pointcut>,切入点通过expression来匹配到需要

  • SpringBoot中使用AOP打印接口日志的方法

    前言 AOP 是 Aspect Oriented Program (面向切面)的编程的缩写.他是和面向对象编程相对的一个概念.在面向对象的编程中,我们倾向于采用封装.继承.多态等概念,将一个个的功能在对象中来实现.但是,我们在实际情况中也发现,会有另外一种需求就是一类功能在很多对象的很多方法中都有需要.例如有一些对数据库访问的方法有事务管理的需求,有很多方法中要求打印日志.按照面向对象的方式,那么这些相同的功能要在很多地方来实现或者在很多地方来调用.这就非常繁琐并且和这些和业务不相关的需求耦合太

  • 在AOP中Spring生成代理类的两种方式

    Java 动态代理.具体有如下四步骤: 通过实现 InvocationHandler 接口创建自己的调用处理器: 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类: 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型: 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入. 在AOP中,Spring通过生成代理类,来完成切面的织入. Spring生成代理类有2种方式. 如果目标对象实现的是一个接口,Sprin

  • Spring Boot之AOP配自定义注解的最佳实践过程

    前言 AOP(Aspect Oriented Programming),即面向切面编程,是Spring框架的大杀器之一. 首先,我声明下,我不是来系统介绍什么是AOP,更不是照本宣科讲解什么是连接点.切面.通知和切入点这些让人头皮发麻的概念. 今天就来说说AOP的一些应用场景以及如何通过和其他特性的结合提升自己的灵活性.下面话不多说了,来一起看看详细的介绍吧 AOP应用举例 AOP的一大好处就是解耦.通过切面,我们可以将那些反复出现的代码抽取出来,放在一个地方统一处理. 同时,抽出来的代码很多是

  • Spring AOP如何整合redis(注解方式)实现缓存统一管理详解

    前言 项目使用redis作为缓存数据,但面临着问题,比如,项目A,项目B都用到redis,而且用的redis都是一套集群,这样会带来一些问题. 问题:比如项目A的开发人员,要缓存一些热门数据,想到了redis,于是乎把数据放入到了redis,自定义一个缓存key:hot_data_key,数据格式是项目A自己的数据格式,项目B也遇到了同样的问题,也要缓存热门数据,也是hot_data_key,数据格式是项目B是自己的数据格式,由于用的都是同一套redis集群,这样key就是同一个key,有的数据

  • spring boot 使用Aop通知打印控制器请求报文和返回报文问题

    一.简介 开发过程中我们往往需要写许多例如: @GetMapping("/id/get") public Result getById( String id) throws Exception{ log.info("请求参数为:"+id); verify(new VerifyParam("部门id", id)); Result result = new Result("通过id获取部门信息成功!", service.query

  • Spring中IOC和AOP的深入讲解

    前言 Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来.它是为了解决企业应用开发的复杂性而创建的.Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情.然而,Spring的用途不仅限于服务器端的开发.从简单性.可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益

  • Spring AOP + 注解实现统一注解功能

    1. 概述 在一般系统中,当我们做了一些重要的操作时,如登陆系统,添加用户,删除用户等操作时,我们需要将这些行为持久化.本文我们通过Spring AOP和Java的自定义注解来实现日志的插入.此方案对原有业务入侵较低,实现较灵活 2. 日志的相关类定义 我们将日志抽象为以下两个类:功能模块和操作类型 使用枚举类定义功能模块类型ModuleType,如学生.用户模块 public enum ModuleType { DEFAULT("1"), // 默认值 STUDENT("2

  • 详解使用Spring AOP和自定义注解进行参数检查

    引言 使用SpringMVC作为Controller层进行Web开发时,经常会需要对Controller中的方法进行参数检查.本来SpringMVC自带@Valid和@Validated两个注解可用来检查参数,但只能检查参数是bean的情况,对于参数是String或者Long类型的就不适用了,而且有时候这两个注解又突然失效了(没有仔细去调查过原因),对此,可以利用Spring的AOP和自定义注解,自己写一个参数校验的功能. 代码示例 注意:本节代码只是一个演示,给出一个可行的思路,并非完整的解决

随机推荐