使用注释和 lombok 在字段上切入点

Pointcut on fields using annotation and lombok

简介:我正在使用 Java 和 Spring boot 2.2.2 和 Lombok

我得到了这个例子class:

package com.example.demo.classz;


import com.example.demo.pack.MyAnnotation;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor
public class FooClass {

    @NonNull
    @MyAnnotation
    private String str;

    private String uninterceptedString;

}

我想拦截所有对使用@MyAnnotation 注释的"get"/"set" 方法的调用。为了管理这个,我创建了这个界面:

package com.example.demo.pack;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {}

和这个class做一些操作。

package com.example.demo.pack;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class AnnotatedFieldAspect {

    @Before("execution(* com.example.demo.classz.*.get*(..)) && @annotation(com.example.demo.pack.MyAnnotation)")
    public void interceptRead(JoinPoint thisJoinPoint) {
        log.info(thisJoinPoint.toString());
    }

    @Before("execution(* com.example.demo.classz.*.set*(..)) && @annotation(com.example.demo.pack.MyAnnotation) && args(newValue)")
    public void interceptWrite(JoinPoint thisJoinPoint, Object newValue) {
        log.info(thisJoinPoint + " -> " + newValue);
    }
}

最后为了测试一切,我做了一个简单的控制器

package com.example.demo;


import com.example.demo.classz.FooClass;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    @GetMapping("/")
    public String test() {
        FooClass fooClass = new FooClass("Foo");

        fooClass.setStr("Foo2");
        fooClass.getStr();

        return "Hi";
    }
}

我无法激活那些切入点,我不明白为什么。你能帮帮我吗?

我已经在 Whosebug 上看到类似的问题,例如: - Spring AOP Pointcut expression for annotated field - @AspectJ pointcut for all methods of a class with specific annotation

还有其他一些,但即使使用他们的解决方案我也没有成功。

一些配置内容:

@EnableAspectJAutoProxy
@SpringBootApplication
public class DemoApplication {

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

}

来自 build.gradle 的依赖部分:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.session:spring-session-core'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '2.2.2.RELEASE'
//  compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.5'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

谢谢!

正如 kriegaex 提到的,FooClass 不是由 Spring Container 管理的 bean。您只能拦截一个 spring bean。

以下代码更改将使 setter 方法在切入点表达式有效的情况下被拦截。

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

    @Data
    @RequiredArgsConstructor
    @Component
    @Scope(scopeName="prototype")
    public class FooClass {

        @NonNull
        @MyAnnotation
        private String str;

        private String uninterceptedString;

    }

并从应用程序上下文中获取 FooClass bean。

FooClass fooClass = ctx.getBean(FooClass.class,"Foo");
fooClass.setStr("Foo2");
fooClass.getStr();

阅读有关@Component 和@Scope 的更多信息以了解所做的更改。

@Component @Scope

请注意,Spring AOP 只能拦截方法调用。 阅读文档 here

== Spring AOP Capabilities and Goals

Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans). Field interception is not implemented, although support for field interception could be added without breaking the core Spring AOP APIs. If you need to advise field access and update join points, consider a language such as AspectJ.

出于同样的原因,以下 Spring AOP 切入点表达式对您的情况有效

@Before("execution(* com.example.demo.classz.*.get*(..))")
    public void interceptRead(JoinPoint thisJoinPoint) {
        log.info(thisJoinPoint.toString());
    }

    @Before("execution(* com.example.demo.classz.*.set*(..)) && args(newValue)")
    public void interceptWrite(JoinPoint thisJoinPoint, Object newValue) {
        log.info(thisJoinPoint + " -> " + newValue);
    }