Spring的奥秘AOP(四)

简介: 前文讲了很多原理性的东西,使用起来比较繁琐,通过Spring框架提供的注解能很方便的实现切面功能。Spring中使用注解方式实现AOP,采用@AspectJ方式实现,首先确定需要切入的方法,也就是连接点

前文讲了很多原理性的东西,使用起来比较繁琐,通过Spring框架提供的注解能很方便的实现切面功能。


Spring中使用注解方式实现AOP,采用@AspectJ方式实现,首先确定需要切入的方法,也就是连接点


@Service
public class UserServiceMethod {
    public void add(String name) {
        System.out.println("UserServiceMethod add name is:" + name);
    }
}


开发切面


有了连接点,还需要切面通过切面描述AOP其他信息,来描述流程的织入


@Aspect
@Component
public class LogAgent {
    @Before("execution(* com.niu.dao.UserServiceMethod.add(..))")
    public void beforeAdd(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("before 方法规则拦截:" + method.getName());
    }
    @After("execution(* com.niu.dao.UserServiceMethod.add(..))")
    public void afterAdd(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("after 方法规则拦截:" + method.getName());
    }
    @AfterReturning("execution(* com.niu.dao.UserServiceMethod.add(..))")
    public void afterReturnAdd(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("afterReturn 方法规则拦截:" + method.getName());
    }
    @AfterThrowing("execution(* com.niu.dao.UserServiceMethod.add(..))")
    public void afterThrowsAdd(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("afterThrows 方法规则拦截:" + method.getName());
    }
}

其中注解中execution(* com.niu.dao.UserServiceMethod.add(..))是正则匹配,


execution表示正在执行的时候,拦截里面的正则匹配方法


*表示任意返回类型的方法


com.niu.dao.UserServiceMethod指定目标对象的全限定名称


add指定目标对象的方法


(..)表示任意参数进行匹配


AssertJ关于SpringAop切点的指示器:


  • args() 限定连接点方法参数
  • @args() 通过连接点方法参数上的注解
  • execution()用于匹配连接点上的执行方法
  • this()限制连接点匹配AOP代理bean引用为指定的类型
  • target目标对象
  • @target限制目标对象的配置
  • within限制连接点匹配指定的类型
  • @within() 限定连接点带有匹配注解类型
  • @annotation()限定带有指定注解的连接点


定义切点


在上面切面定义中,可以看到@before,@After等注解,还会定义一个正则,这个正则作用就是定义什么时候启用AOP,毕竟不是所有的功能都需要AOP,在上述代码中每个注解都有一个正则,这显得很冗余。为了解决这个问题,Spring定义了切点的概念(PointCut),切点的作用就是向Spring描述哪些类的哪些方法需要启动AOP编程。有了切点的概念,就可以吧代码写的更简洁:


@Aspect
@Component
public class LogAgent2 {
    @Pointcut("execution(* com.niu.dao.UserServiceMethod.add(..))")
    public void pointCut() {
        System.out.println("pointCut");
    }
    @Before("pointCut()")
    public void beforeAdd(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("before 方法规则拦截:" + method.getName());
    }
    @After("pointCut()")
    public void afterAdd(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("after 方法规则拦截:" + method.getName());
    }
    @AfterReturning("pointCut()")
    public void afterReturnAdd(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("after Return 方法规则拦截:" + method.getName());
    }
}

测试AOP,两种方式输出完全一致

@SpringBootApplication
public class App {
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        System.out.println("Start app success.");
        UserServiceMethod userServiceMethod = context.getBean(UserServiceMethod.class);
        userServiceMethod.add();
    }
}
//输出:
Start app success.
before 方法规则拦截:add
UserServiceMethod add 
after 方法规则拦截:add
after Return 方法规则拦截:add


引入


如果想检测用户信息是否为空,入参为空时则不打印,但是原接口中没有该校验,该怎么办呢? 这时应该引入Spring增强接口功能,为接口引入新的接口


首先提供校验接口:


public interface UserValidator {
    boolean validate(String name);
}
public class UserValidatorImpl implements UserValidator {
    @Override
    public boolean validate(String name) {
        System.out.println("引入校验");
        return StringUtils.isEmpty(name);
    }
}

引入新的接口:

@Aspect
@Component
public class MyAspect {
    @DeclareParents(value = "com.niu.dao.UserService+",
            defaultImpl = UserValidatorImpl.class)
    public UserValidator userValidator;
}

这里@DeclareParents作用是引入新的类来增强服务,必须有value和defaultImpl配置:

value:指向要增强功能的目标对象,配置为UserService defaultImpl:引入增强功能的类,这里配置为UserValidatorImpl


public static void main(String[] args) throws Exception {
    ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
    System.out.println("Start app success.");
    UserValidator userService = (UserValidator)context.getBean(UserService.class);
    if(userService.validate("steven")){
        System.out.println("Valid name");
    }
}
输出:Valid name

说明接口强转类型后,可以使用新方法,证明已有UserValidator的方法。


通知获取参数


在上述的通知中,大部分没有通知传递参数,可以通过使用连接点(Joinpoint)获取参数

@Before("pointCut() && args(name)")
public void beforeAdd(JoinPoint joinPoint, String name) {
    System.out.println(Arrays.toString(joinPoint.getArgs()));;
    System.out.println(name);;
}
输出:
[steven]
steven
复制代码

正则式pointCut() && args(name)中pointCut()表示原来定义的切点规则,并且约定将连接点(目标对象方法)名称为name的参数传递进来,通过连接点getArgs可以获取到所有参数,对于连接点参数还可以获取到目标对象的信息,从而完成需要的切面任务,下面是不用入参的形式:

@Before("pointCut()")
public void beforeAdd(JoinPoint joinPoint) {
    System.out.println(Arrays.toString(joinPoint.getArgs()));;
}
输出:
[steven]


织入


织入是一个生成动态代理对象并将切面的目标对象方法编织为约定的流程,一般都是采用接口加实现类的模式,这也是Spring推荐的方式,前问介绍过动态代理有很多实现方式JDK、CGLIB等,查资料说在Spring中如果要实现AOP的类有接口则用JDK动态代理执行,如果没有接口则用CGLib运行


跟下断点验证一下,我试过两种方式,不管实现类有没有接口都是使用的CGlib,不知道是不是网上解释有误还是SpringBoot2采用方式变更


image.png


多个切面


Spring支持多个切面运行

提供三个切面

@Aspect
@Component
public class LogAgent {
    @Before("execution(* com.niu.dao.UserServiceMethod.add(..))")
    public void beforeAdd(JoinPoint joinPoint) {
        System.out.println("LogAgent 拦截");
    }
}
@Aspect
@Component
public class LogAgent2 {
    @Pointcut("execution(* com.niu.dao.UsrTestService.add(..))")
    public void pointCut() {
        System.out.println("pointCut");
    }
    @Before("pointCut()")
    public void beforeAdd(JoinPoint joinPoint) {
        System.out.println("LogAgent2 拦截");;
    }
}
@Aspect
@Component
public class LogAgent3 {
    @Pointcut("execution(* com.niu.dao.UsrTestService.add(..))")
    public void pointCut() {
        System.out.println("pointCut");
    }
    @Before("pointCut()")
    public void beforeAdd(JoinPoint joinPoint) {
        System.out.println("LogAgent3 拦截");;
    }
}
输出:
LogAgent3 拦截
LogAgent 拦截
LogAgent2 拦截

结果每次输出拦截的顺序是不固定的,如果需要固定的执行的顺序可以在类上加Order注解,比如:

@Aspect
@Component
@Order(1)
public class LogAgent2 {
    @Pointcut("execution(* com.niu.dao.UsrTestService.add(..))")
    public void pointCut() {
        System.out.println("pointCut");
    }
}
输出:
LogAgent2 拦截
LogAgent3 拦截
LogAgent 拦截

可以根据需要排序,如果再加上after方法的打印,可以看到这是一个典型的职责链模型的顺序,有兴趣的可以自己在研究

LogAgent2 拦截
LogAgent3 拦截
LogAgent 拦截
UserServiceMethod add name is:steven
LogAgent1 :after 方法规则拦截:add
LogAgent3:after 方法规则拦截:add
LogAgent2:after 方法规则拦截:add


结语


关于AOP的文章就写到这了,功能其实并不难,重要的思想,平常开发中也可多考虑这种解耦方式。关于这两种实现的效率问题找了一些资料:在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快,这块有兴趣的可以再研究下,目前效率应该是差不多的。


目录
相关文章
|
3月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
2天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
22天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
29 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
7天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
18 1
|
3天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
8 0
|
2月前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
1月前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
51 2
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
120 9
|
1月前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
44 0
|
2月前
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
Spring基础3——AOP,事务管理
下一篇
无影云桌面