Spring FrameWork从入门到NB - Spring AOP(实战)

news/2024/7/6 0:27:33

这篇文章的目的是对上一篇文章有关AOP概念的温习和巩固,通过实战的方式。

引入依赖

首先需要引入Spring Aspect依赖

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

@EnableAspectJAutoProxy

先声明一下,例子都使用注解方式实现,不用xml配置的方式了,其实区别不大,但是本人不喜欢xml配置的方式。

Configuration文件中加入@EnableAspectJAutoProxy注解。

@Configuration()
@ComponentScan(value={"springAop"})
@EnableAspectJAutoProxy
@PropertySource("classpath:application.properties")
@EnableAsync
public class MyConfiguration {
}

业务类

准备一个业务类UserService,目标就是为了实现AOP功能,所以业务类不需要实现任何业务逻辑:

public interface IUserService {
    public boolean addUser();
    public boolean deleteUser();
    public boolean updateUser();
}

IUserService的实现类UserService,为了验证AfterThrowing类的Advice,我们将updateUser方法直接抛出异常,模拟业务类抛异常的场景:

package springAop;

import org.springframework.stereotype.Component;

@Component
public class UserService implements IUserService{
    @Override
    public boolean addUser() {
        System.out.println("add user in userService");
        return true;
    }

    @Override
    public boolean deleteUser() {
        System.out.println("delete user in userService");
        return false;
    }

    @Override
    public boolean updateUser() {
        throw new RuntimeException("cant be updated...");
    }
}

目前为止涉及到的Spring AOP的概念:目标对象,也就是Target object,本例中目标对象就是UserService。

编写切面

编写一个用于记录日志的切面,我们需要一步步认识切面类中涉及到的AOP概念或术语。

下面用于处理日志切面的类LogManagement就是一个Aspect:

@Aspect
@Component
public class LogManagement {
}

对于Spring AOP来说,切面必须注入到Spring IoC容器中,所以也必须加@Component注解,以及,表明自己是切面类的@Aspect注解。

下面我们给切面类加pointcut:

    @Pointcut("execution(* springAop.UserService.add*())")
    public void addUserPointcut(){}

@Pointcut注解表示其作用的方法addUserPointcut是一个切点,在满足切点定义的连接条件的情况下会被Spring AOP调用到,去执行相应的Advice。

@Pointcut注解的参数:"execution(* springAop.UserService.add*())"定义切点表达式,通过表达式指定当前切点与什么JointPoint关联,我们这里指定为SpringAop.UserService类的以add开头的方法,前面的 * 匹配JointPoint方法的返回值,*表示任意返回值。

通过 @Pointcut注解,我们详单与定义了AOP的JointPoint和Pointcut两个特性。

接下来需要定义Advices了。

暂时只定义一个Before Advice:

@Before(value="addUserPointcut()")
    public void beforeLog(JoinPoint jp){
        System.out.println("before "+jp+" advice...");
    }

通过@Before注解指定,匹配到我们前面定义好的addUserPointcut。我们在前面的文章中说过,除了Around类型的Advice可以参与目标对象的方法调用之外,其他类型的Advice只能增强方法调用、但是没有办法参与目标对象的方法调用,目标对象的方法调用交给AOP框架完成。

虽然没有办法参与目标对象的方法调用,但是@Before Advice可以有参数JoinPoint,给Advice一个机会,能知道当前AOP的增强调用匹配到的是哪一个JoinPoint,正如我们例子中看到的,我们在Advice方法beforeLog中加了这个JointPoint参数。

准备一下启动类:

public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
        IUserService us = (IUserService)  applicationContext.getBean("userService");
//        UserService us = (UserService)  applicationContext.getBean("userService");
        System.out.println(us.getClass());
        us.addUser();
        us.deleteUser();
        try {
            us.updateUser();
        }catch (Exception e){

        }

运行启动类,看一下效果。

before execution(boolean springAop.IUserService.addUser()) advice...
add user in userService

Before Advice已经生效了。

其他类型的Advice

现在我们增加其他类型的Advices:

package springAop;

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

import java.util.jar.JarOutputStream;

@Aspect
@Component
public class LogManagement {
    @Pointcut("execution(* springAop.UserService.add*())")
    public void addUserPointcut(){}
    @Pointcut("execution(* springAop.UserService.update*())")
    public void updateUserPointcut(){}

    @Before(value="addUserPointcut()")
    public void beforeLog(JoinPoint jp){
        System.out.println("before "+jp+" advice...");
    }
    @After(value="addUserPointcut()")
    public void afterLog(JoinPoint jp){
        System.out.println(" after "+jp+" advice......");
    }
    @Around(value="addUserPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println(" This is around " +pjp+" ,before process...");
        Object retVal=pjp.proceed();
        System.out.println("the return value after proceed is :"+retVal);
        System.out.println(" This is around" +pjp+" ,after process...");
        return retVal;
    }

    @AfterReturning(value="addUserPointcut()")
    public void afterRreturning(JoinPoint jp) throws Throwable{
        System.out.println(" This is after returning" +jp+" ...");
    }

    @AfterThrowing(value="updateUserPointcut()")
    public void afterThrowing(JoinPoint jp) throws Throwable{
        System.out.println(" This is after throwing" +jp+" ...");
    }

}

运行启动类:

class com.sun.proxy.$Proxy34
 This is around execution(boolean springAop.IUserService.addUser()) ,before process...
before execution(boolean springAop.IUserService.addUser()) advice...
add user in userService
the return value after proceed is :true
 This is aroundexecution(boolean springAop.IUserService.addUser()) ,after process...
 after execution(boolean springAop.IUserService.addUser()) advice......
 This is after returningexecution(boolean springAop.IUserService.addUser()) ...
delete user in userService
 This is after throwingexecution(boolean springAop.IUserService.updateUser()) ...

分析一下运行结果,可以得出如下重要结论:

  1. 从Spring Ioc容器中获取到的UserService对象已经不是原对象了,而是代理对象com.sun.proxy.$Proxy34
  2. 发现代理对象是JDK Proxy实现的,这是由于我们的UserService类实现了IUserService接口,具备JDK Proxy的实现条件,如果我们去掉UserService的IUserService接口、或者在配置类中打开proxyTargetClass选项:@EnableAspectJAutoProxy(proxyTargetClass = true),都可以更换为Cglib方式生成代理对象
  3. 一个方法上同时加了多了Advice之后的生效顺序:around(执行目标方法前)-> before -> around执行目标方法 -> after ->after returning。
  4. 额外的,篇幅原因我们就不增加测试代码了:After可以获取到JointPoint的任何返回(调用成功或发生异常),而AferRetuning只能获取到正常返回。

如果我们的一个方法有多个切面类生效的话,执行顺序就会类似于FilterChain的剥洋葱的方式。

AfterThrowing

与其他类型的Advice不同的是,某些情况下,我们在AfterThrowing Advice下可能想知道业务类具体抛出了什么样的异常,从而在AOP Advice中针对不同类型的异常做有针对性的处理。

AOP的AfterThrowing Advice提供了这个能力,修改上面例子中的AfterThrowing Advice:

    @AfterThrowing(value="updateUserPointcut()",throwing="ex")
    public void afterThrowing(JoinPoint jp,Throwable ex){
        System.out.println(" This is after throwing" +jp+" ...");
        System.out.println(ex);
    }

重新执行启动类:

 This is after throwingexecution(boolean springAop.UserService.updateUser()) ...
java.lang.RuntimeException: cant be updated...

我们在UserService的updateUser方法中抛出的异常,在AOP Advice中可以获取到,因此也就可以在AOP的Advice中做有针对性的处理。

此外,对异常的处理,比如捕获还是抛出的最终决策,是由应用程序做成的,AOP切面对于应用来说就是一个旁观者:对应用不做浸入式的处理(Around除外,Around具备浸入处理的能力,虽然不能改变JointPoint的业务逻辑,但是可以改变其运行结果、返回切面自己的结果)。

Advice获取返回值

Aop切面类的after Advice方法中可以获取到业务类的返回值,比如:

    @AfterReturning(value="addUserPointcut()",returning = "ret")
    public void afterRreturning(JoinPoint jp,Object ret) throws Throwable{
        System.out.println(" This is after returning" +jp+" ...");
        System.out.println("return :"+ret);
    }

执行启动类:

 This is after returningexecution(boolean 	   springAop.UserService.addUser()) ...
  return :true

Advice参数处理

AOP提供了切面类Advice中获取目标类JointPoint的参数的能力。

改造IUserService接口和UserService类,addUser增加一个userName参数:

    @Override
    public boolean addUser(String userName) {
        System.out.println("add user in userService:"+userName);
        return true;
    }

改造切面类,Advice接收参数:

 @Pointcut("execution(* springAop.UserService.add*(..)) && args(userName)")
    public void addUserPointcut(String userName){}

Advice做相应的改造:

    @Before(value="addUserPointcut(userName)")
    public void beforeLog(JoinPoint jp,String userName){
        System.out.println("before "+jp+" advice..."+" and userName is :"+userName);
    }

执行启动类:

before execution(boolean springAop.UserService.addUser(String)) advice... and userName is :Zhang San

Advice中获取到了应用层的JointPoint方法的参数。

OK,今天到此为止,通过实战方式重温了一遍AOP相关术语之后,有没有感觉到AOP的术语不再那么晦涩难懂了?

上一篇 Spring FrameWork从入门到NB - Spring AOP - 概念


http://www.niftyadmin.cn/n/4073029.html

相关文章

asyncio + pycurl + BytesIO 异步批量调用url请求

import asyncioimport pycurlfrom io import BytesIOimport json def fetch_api(url, method, headerNone, dataNone):"""url&#xff1a; 获取api的urlmethod: 请求方法header: 请求头data: 请求参数""" if method "get":_Curl pyc…

《中国人工智能学会通讯》——1.2 问答与智能信息获取

1.2 问答与智能信息获取 问答系统作为智能表征的研究领域&#xff0c;几十年来一直受到学术界的关注&#xff0c;国际评测 TREC 历经十余年对问答系统从几个方面进行了评测[4] 。问答系统的发展杂问题的发展过程&#xff0c;逐步具有了更多的智能行为特性。这个过程并不是一个单…

Ubuntu的穆斯林版 UbuntuME

Ubuntu Muslim Edition&#xff0c;Ubuntu穆斯林版&#xff1a;基于Ubuntu&#xff0c;加入了很多伊斯兰相关的软件。Ubuntu Muslim Edition团队最近释放了基于Ubuntu 7.10 (Gutsy Gibbon)的UbuntuME 7.10。 和标准版Ubuntu不同的是&#xff1a; 删除了GNOME游戏&#xff08;GN…

call function

1 call递归扩展变量 本质上仍然是变量扩展&#xff0c;等价于$()&#xff0c;只不过扩展的时候带了参数&#xff0c;$(call xxx)返回的是xxx扩展之后的值。参数依次赋值给$(1),$(2)......&#xff0c;但是参数要在赋值之前完成扩展。 2 call define定义的多行变量 本质上仍然是…

Spring Boot启动原理解析

Spring Boot启动原理解析http://www.cnblogs.com/moonandstar08/p/6550758.html 前言 前面几章我们见识了SpringBoot为我们做的自动配置&#xff0c;确实方便快捷&#xff0c;但是对于新手来说&#xff0c;如果不大懂SpringBoot内部启动原理&#xff0c;以后难免会吃亏。所以这…

Linux strace

Linux strace strace 示例 1.跟踪nginx, 看其启动时都访问了哪些文件strace -tt -T -f -e tracefile -o /data/log/strace.log -s 1024 2>&1 ./nginx2. 定位程序异常退出strace -ttf -T -p 10893 -o tmp -e traceprocess 2>&13.程序启动加载文件strace -e open,a…

Intel强势入驻 Cloudera新一轮融资9亿

在 “15核芯片数据平台&#xff0c;Intel软硬兼备的Hadoop战略”一文中&#xff0c;我们有报道过Intel发布了新型的Xeon处理器&#xff08;该系列被用来支持多达32个插槽的服务器&#xff0c;并配有15个处理核心&#xff0c;每个插槽分配1.5TB内存&#xff09;&#xff0c;与 I…

IPv9(十进制网络)技术简介

IPv9(十进制网络)技术简介 根据《采用全数字码给上网的计算机分配地址的方法》发明专利实施并发展而成的“十进制网络”采用的是自主知识产权、以十进制算法(0-9)为基础的IPV9协议&#xff0c;并将网上计算机进行互相连接&#xff0c;从而达到计算机相互通信和数据传输的目的。…