簡體   English   中英

如何在Spring Bean上強制/驗證Spring范圍注釋

[英]How to enforce/verify spring scope annotation on spring beans

我們完全由注釋驅動,並且不使用XML文件進行彈簧配置。

spring bean的默認范圍是單例,許多開發人員忘記了這些,最終創建了應該改變范圍的bean。 問題的復雜性增加了各種作用域bean的混合和匹配。

是否有任何Maven插件可以檢查具有@Component批注的任何類是否也具有@Scope批注,如果缺少,則構建失敗。 這將迫使開發人員考慮范圍和使用模式。 如果不存在類似的東西,我可以編寫插件或使用一個自定義工具檢查該問題並在jenkins構建過程中觸發。 春季代碼中有什么可以幫助我做到這一點嗎?

另外,如果spring bean中有@Autowire注釋,是否有一種方法可以驗證注入的bean具有正確的作用域。 我正在假設您將原型作用域bean注入單例作用域bean中,最有可能的不是您想要的。 盡管在某些情況下這可能是開發人員想要的,但就我們而言,到目前為止,這主要是開發人員的錯誤。

您可以使用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方法或字段讀取訪問以間接匹配字段來解決此問題。 如果您自己無法解決,請隨時詢問該怎么做。

我能想到的最簡單的想法是使用Checkstyle的RegexpSinglelineJava檢查來確保每個文件中只有1個@Scope: http ://checkstyle.sourceforge.net/config_regexp.html#RegexpSinglelineJava

由於您需要檢查兩個注釋,因此RegexpMultiline檢查可能會起作用(將需要一致地排列注釋): http ://checkstyle.sourceforge.net/config_regexp.html#RegexpMultiline

最近,我還需要驗證@Autowired bean的范圍,並且找不到任何合適的即用型解決方案。 因此,我創建了一個小型項目 ,該項目允許在運行時驗證bean范圍。 默認情況下,它允許進行以下注入:

  • 單身人士可以注入一切
  • 一切都可以注入原型
  • AOP代理可以注入所有內容
  • 一切都可以注入到相同范圍的bean中

如果要允許將bean注入另一個作用域,則需要使用相應的注釋明確允許它:

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

如果bean在運行時使用了不允許范圍的依賴關系,則它可以記錄它,拋出異常(從而阻止bean的創建)或執行任何自定義操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM