Spring AOP通知不生效问题

大佬们,我初学Spring AOP,碰到了切面类的通知方法不启动的问题,想求大佬深入讲解一下原因,具体的问题有:

  1. 在PointCut中设置了目标类中所有的方法,构造方法为什么不会启动通知?

  2. 直接调用目标方法,为什么不会启动通知?

1. 首先我快速搭了一个springboot web项目,确定可以正常运行,然后创建了一个controller类

@RestController
public class HelloWorld {
    private String words;

    HelloWorld(){
        this.words = "HelloWorld!";
        this.say();
    }

    @RequestMapping("/hello")
    public void say(){
        System.out.println(this.words);
        GoodBye goodBye = new GoodBye();
        goodBye.say();
    }
}

2. 然后我创建了一个切面类

@Aspect
@Component
public class FirstAOP {
    @Pointcut(value = "execution(* com.springaop.HelloWorld.*(..))")
    public void logAOP(){

    }

    @Around(value = "logAOP()")
    public Object doAround(@NotNull ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around");
        return pjp.proceed();
    }

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

    @After(value = "logAOP()")
    public void doAfter(){
        System.out.println("after");
    }
}

3. 我在main方法中调用HelloWorld中的say()

    public static void main(String[] args) {
        SpringApplication.run(SpringAopApplication.class, args);

        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(HelloWorld .class);
        annotationConfigApplicationContext.refresh();
        HelloWorld hello= annotationConfigApplicationContext.getBean(HelloWorld .class);
        hello.say();
    }

4. 结果

最后控制台只是输出了 HelloWorld! ,在网页端输入 localhost:8080/hello 地址以后控制台再次输出,这时才有AOP通知的内容


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.6)

2022-04-20 15:56:09.929  INFO 16216 --- [           main] com.springaop.SpringAopApplication       : Starting SpringAopApplication using Java 1.8.0_271
2022-04-20 15:56:09.940  INFO 16216 --- [           main] com.springaop.SpringAopApplication       : No active profile set, falling back to 1 default profile: "default"
2022-04-20 15:56:10.718  INFO 16216 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-04-20 15:56:10.726  INFO 16216 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-04-20 15:56:10.726  INFO 16216 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.60]
2022-04-20 15:56:10.887  INFO 16216 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-04-20 15:56:10.887  INFO 16216 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 913 ms
HelloWorld!
2022-04-20 15:56:11.318  INFO 16216 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-04-20 15:56:11.326  INFO 16216 --- [           main] com.springaop.SpringAopApplication       : Started SpringAopApplication in 1.751 seconds (JVM running for 2.642)
2022-04-20 15:56:13.637  INFO 16216 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-04-20 15:56:13.637  INFO 16216 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-04-20 15:56:13.638  INFO 16216 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
around
before
HelloWorld!
after

Process finished with exit code 130

求大佬解答!

我没这样写过。我是这样认为的。

  AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(HelloWorld .class);
        annotationConfigApplicationContext.refresh();
        HelloWorld hello= annotationConfigApplicationContext.getBean(HelloWorld .class);
        hello.say();

上面这几行代码,创建了一个新的Application Context。注册了一个 HelloWorld.class,IOC进行了实例化,仅此而已。所以你的Aop没有生效。

但是处理你请求的HelloWorld 却是springboot的ioc中的示例。springboot加载了你的Aop配置。所以执行的时候切面方法生效。

通俗点说,你两个HelloWorld 实例对象应该不是同一个。你可以打印看看。

你可以尝试把你的代码修改为这样:

ApplicationContext applicationContext = SpringApplication.run(SpringAopApplication.class, args);
applicationContext.getBean(HelloWorld.class).say();

是的,我按照您的思路看了一下,AOP响应的应该是启动时就被加入容器的HelloWorld.class,后续我使用ApplicationContext注册的并没有生效。

但是遗憾的是使用您的改进方法以后仍旧没有生效。
我想知道AOP应该不是只能作用于处理RequestMapping等请求方法的吧

不是,它可以作用于所有IOC中的实例。

所以我是否可以简单地认为:

  1. 构造方法不会使AOP生效是因为构造方法执行时类正在IOC的实例化过程中;
  2. 类被IOC实例化后,调用其方法时如果加载了AOP配置,其方法执行时切面方法就会生效;
1 Like

谢谢大佬!关于这个问题我继续进行了实验,发现确实如此

1.我创建了新的类GoodBye,同时改写了HelloWorld类和PointCut的切面

@RestController
public class HelloWorld {
    private String words;

    @Autowired
    GoodBye goodBye;

    HelloWorld(){
        this.words = "HelloWorld!";
    }

    @RequestMapping("/hello")
    public void say(){
        System.out.println(this.words);
        goodBye.say();
    }
}
@Component
public class GoodBye {
    public void say(){
        System.out.println("Good bye!");
    }
}
    @Pointcut(value = "execution(* com.springaop..*.*(..)) ")
    public void logAOP(){

    }

2. 执行以后的结果确实如此

around
before
HelloWorld!
around
before
Good bye!
after
after
  1. 首先IOC加载了HelloWorld.class和GoodBye.class;
  2. 然后在HelloWorld响应/hello请求的输出中发现springboot已经加载了AOP配置,并且成功生效了;
  3. 同时另一个实例化的非Controller类GoodBye的AOP也生效。
1 Like