如何在spring bean上强制执行/验证spring范围注释

时间:2014-11-21 19:15:12

标签: java spring maven dependency-injection

我们完全注释驱动,不使用XML文件进行弹簧配置。

spring bean的默认范围是singleton,许多开发人员忘记了这个范围,最终创建了应该采用不同范围的bean。增加了各种范围豆的混合和匹配问题的复杂性。

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

此外,如果spring bean中有@Autowire注释,是否有办法验证正在注入的bean是否具有正确的范围。如果你在singleton scoped bean中注入原型scoped bean,我正在使用这个假设,很可能那不是你想要的。虽然可能存在开发人员想要的用例,但在我们的例子中,到目前为止这主要是开发人员的错误。

3 个答案:

答案 0 :(得分:0)

您可以使用AspectJ根据切入点声明错误和/或警告的能力。

免责声明: 我从来没有使用过Spring,因此我不是那里的专家而只是编写一个没有太多意义的示例。

原型范围的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方法连接。如果取消注释字段声明中的注释,您甚至可以使用其他类型的连线。这没有意义,但我们希望稍后在某个方面引发编译错误。

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的控制台输出:

(...)
[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方法或字段读取访问来间接匹配字段。如果你不能自己解决,请随时问一下如何做到这一点。

答案 1 :(得分:0)

我能想到的最简单的想法是使用Checkstyle的RegexpSinglelineJava检查来确保每个文件只存在1个@Scope: http://checkstyle.sourceforge.net/config_regexp.html#RegexpSinglelineJava

由于您需要检查两个注释,因此RegexpMultiline检查可能会起作用(需要一致地注释注释): http://checkstyle.sourceforge.net/config_regexp.html#RegexpMultiline

答案 2 :(得分:0)

我最近还需要验证@Autowired bean的范围,并且找不到任何合适的即用型解决方案。因此,我创建了一个小的project,它允许在运行时验证bean范围。默认情况下,它允许进行以下注入:

  • 可以将东西注入所有东西
  • 一切都可以注入原型
  • 可以将AOP代理注入所有内容
  • 一切都可以注入到相同范围的豆子中

如果要允许将bean注入另一个作用域,则需要使用相应的注释明确允许它:

@Bean
@Scope("prototype")
@InjectableInto("singleton")
MyBean getMyBean(){
 //...
} 

如果运行时bean使用了不允许范围的依赖关系,则它可以记录它,引发异常(从而阻止创建bean)或执行任何自定义操作。