如何强制/验证 spring bean 上的 spring 范围注释 [英] How to enforce/verify spring scope annotation on spring beans

查看:66
本文介绍了如何强制/验证 spring bean 上的 spring 范围注释的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们完全是注解驱动的,不使用 XML 文件进行 spring 配置.

We are fully annotation driven and do not use XML files for spring configuration.

spring bean 的默认范围是单例,许多开发人员忘记了这一点,并最终创建了应该具有不同范围的 bean.增加了各种作用域 bean 混合和匹配问题的复杂性.

Default scope of spring beans is singleton which many developers forget and end up in creating beans that should be differently scoped. Added to the complexity of problems mix and match of various scoped beans.

是否有任何 maven 插件可以检查具有 @Component 注释的任何类是否也具有 @Scope 注释,如果缺少则构建失败.这将迫使开发人员考虑范围和使用模式.如果不存在类似的东西,我可以编写插件或使用自定义工具来检查并在 jenkins 构建期间触发.任何弹簧代码都可以帮助我做到这一点吗?

Is there any maven plugin that can check if any class that has @Component annotation also has @Scope annotation and fail the build if its missing. This will force developers to think about the scope and usage patterns. If something similar does not exists, I can write the plugin or have a custom tool that can check this and fire during jenkins build. Can any of spring code help me to do this?

此外,如果 spring bean 中有 @Autowire 注释,有没有办法验证注入的 bean 具有正确的作用域.如果您在单例作用域 bean 中注入原型作用域 bean,我正在假设,这很可能不是您想要的.尽管可能存在开发人员想要的用例,但在我们的案例中,到目前为止,这主要是开发人员的错误.

Additionally if there is @Autowire annotation in spring bean, is there a way to validate that beans being injected have right scopes. I am working with the assumption if you inject prototype scoped bean in singleton scoped bean, most likely that is not what you wan. Though there might be use cases where this is what developer want, in our case, so far this is mostly been developer mistake.

推荐答案

您可以使用 AspectJ 的功能基于切入点声明错误和/或警告.

You can use AspectJ's ability to declare errors and/or warnings based on pointcuts.

免责声明:我从来没有用过 Spring,所以我不是那里的专家,只是举了一个例子,没有太多的演示意义.

Disclaimer: I have never used Spring, so I am not an expert there and just making up an example without much sense for demonstration.

具有原型作用域的 Spring bean:

package de.scrum_master.app;

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

@Component
@Scope("prototype")
public class ScopedBean {}

缺少范围声明的 Spring bean:

package de.scrum_master.app;

import org.springframework.stereotype.Component;

@Component
public class UnscopedBean {}

Spring bean 使用不同类型的自动装配:

这个 bean 使用构造函数和 setter 方法连接.如果您取消注释字段声明上的注释,您甚至可以使用另一种类型的接线.这是没有意义的,但我们想在以后在一个方面引发编译错误.

This bean uses constructor and setter method wiring. If you uncomment the annotation on the field declaration you can even use another type of wiring. This does not make sense, but we want to provoke compilation errors in an aspect later.

package de.scrum_master.app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("singleton")
public class BeanWithAutowire {
    //@Autowired
    private ScopedBean scopedBean;

    @Autowired
    public BeanWithAutowire(ScopedBean scopedBean) {
        this.scopedBean = scopedBean;
    }

    @Autowired
    public void setScopedBean(ScopedBean scopedBean) {
        this.scopedBean = scopedBean;
    }
}

静态注解一致性检查方面:

package de.scrum_master.aspect;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

public aspect BeanAnnotationChecker {
    declare error :
        @annotation(Component) && !@annotation(Scope) :
        "Spring component without scope declaration found";

    declare error :
        execution(@Autowired *.new(.., @Scope("prototype") *, ..)) && within(@Scope("singleton") *) :
        "singleton bean auto-wired into prototype container via constructor";

    declare error :
        execution(@Autowired * *(.., @Scope("prototype") *, ..)) && within(@Scope("singleton") *) :
        "singleton bean auto-wired into prototype container via setter method";

    declare error :
        set(@Autowired * *) && within(@Scope("singleton") *) :
        "singleton bean auto-wired into prototype container via field assignment";
}

使用 AspectJ 编译器的 Maven POM:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>de.scrum-master.stackoverflow</groupId>
    <artifactId>aspectj-fail-build</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>AspectJ - fail build for wrong/missing annotations</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.source-target.version>1.7</java.source-target.version>
        <aspectj.version>1.8.4</aspectj.version>
        <main-class>de.scrum_master.app.ScopedBean</main-class>
    </properties>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>${java.source-target.version}</source>
                        <target>${java.source-target.version}</target>
                        <!-- IMPORTANT -->
                        <useIncrementalCompilation>false</useIncrementalCompilation>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>aspectj-maven-plugin</artifactId>
                    <version>1.7</version>
                    <configuration>
                        <showWeaveInfo>true</showWeaveInfo>
                        <source>${java.source-target.version}</source>
                        <target>${java.source-target.version}</target>
                        <Xlint>ignore</Xlint>
                        <complianceLevel>${java.source-target.version}</complianceLevel>
                        <encoding>UTF-8</encoding>
                        <verbose>true</verbose>
                    </configuration>
                    <executions>
                        <execution>
                            <!-- IMPORTANT -->
                            <phase>process-sources</phase>
                            <goals>
                                <goal>compile</goal>
                                <goal>test-compile</goal>
                            </goals>
                        </execution>
                    </executions>
                    <dependencies>
                        <dependency>
                            <groupId>org.aspectj</groupId>
                            <artifactId>aspectjtools</artifactId>
                            <version>${aspectj.version}</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </pluginManagement>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.7.RELEASE</version>
        </dependency>
    </dependencies>

</project>

mvn clean package 的控制台输出:

Console output for mvn clean package:

(...)
[INFO] ------------------------------------------------------------------------
[INFO] Building AspectJ - fail build for wrong/missing annotations 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(...)
[ERROR] singleton bean auto-wired into prototype container via constructor
    C:\Users\Alexander\Documents\java-src\SO_AJ_MavenFailBuildOnWrongAnnotation\src\main\java\de\scrum_master\app\BeanWithAutowire.java:14
public BeanWithAutowire(ScopedBean scopedBean) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[ERROR] singleton bean auto-wired into prototype container via setter method
    C:\Users\Alexander\Documents\java-src\SO_AJ_MavenFailBuildOnWrongAnnotation\src\main\java\de\scrum_master\app\BeanWithAutowire.java:19
public void setScopedBean(ScopedBean scopedBean) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[ERROR] Spring component without scope declaration found
    C:\Users\Alexander\Documents\java-src\SO_AJ_MavenFailBuildOnWrongAnnotation\src\main\java\de\scrum_master\app\UnscopedBean.java:6
public class UnscopedBean {}
             ^^^^^^^^^^^

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
(...)

我认为除了 AspectJ 语法之外,该示例在某种程度上是不言自明的,但您可以在 AspectJ 手册或教程中阅读更多相关内容.如果您取消注释字段声明中的 @Autowired 注释,您将看到更多关于显式字段分配的错误.但是,AspectJ 不能仅匹配字段声明(没有赋值).因此,每当您的开发人员依赖字段注释而不是带注释的构造函数或 setter 方法时,即您的代码中没有任何显式的字段分配,就不会出现编译错误.您可以通过在代码中匹配 getter 方法或字段读取访问来间接匹配字段来解决这个问题.如果您自己无法弄清楚,请随时询问如何做到这一点.

I think the example is somewhat self-explanatory except for the AspectJ syntax, but you can read more about that in AspectJ manuals or tutorials. If you uncomment the @Autowired annotation on the field declaration, you will see even more errors for explicit field assignments. AspectJ cannot match on a mere field declaration (without assignment), though. So whenever your developers rely on field annotations instead of annotated constructors or setter methods, i.e. you do not have any explicit field assignment in your code, there will be no compilation error. You can work around that by matching on getter methods or field read access in your code to indirectly match on fields. Feel free to ask how to do that if you cannot figure it out by yourself.

这篇关于如何强制/验证 spring bean 上的 spring 范围注释的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆