简体   繁体   English

如何在Spring Bean上强制/验证Spring范围注释

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

We are fully annotation driven and do not use XML files for spring configuration. 我们完全由注释驱动,并且不使用XML文件进行弹簧配置。

Default scope of spring beans is singleton which many developers forget and end up in creating beans that should be differently scoped. spring bean的默认范围是单例,许多开发人员忘记了这些,最终创建了应该改变范围的bean。 Added to the complexity of problems mix and match of various scoped beans. 问题的复杂性增加了各种作用域bean的混合和匹配。

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. 是否有任何Maven插件可以检查具有@Component批注的任何类是否也具有@Scope批注,如果缺少,则构建失败。 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. 如果不存在类似的东西,我可以编写插件或使用一个自定义工具检查该问题并在jenkins构建过程中触发。 Can any of spring code help me to do this? 春季代码中有什么可以帮助我做到这一点吗?

Additionally if there is @Autowire annotation in spring bean, is there a way to validate that beans being injected have right scopes. 另外,如果spring bean中有@Autowire注释,是否有一种方法可以验证注入的bean具有正确的作用域。 I am working with the assumption if you inject prototype scoped bean in singleton scoped bean, most likely that is not what you wan. 我正在假设您将原型作用域bean注入单例作用域bean中,最有可能的不是您想要的。 Though there might be use cases where this is what developer want, in our case, so far this is mostly been developer mistake. 尽管在某些情况下这可能是开发人员想要的,但就我们而言,到目前为止,这主要是开发人员的错误。

You can use AspectJ's ability to declare errors and/or warnings based on pointcuts. 您可以使用AspectJ的功能根据切入点声明错误和/或警告。

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,所以我不是那里的专家,并且只是举了一个没有太多示范意义的示例。

Spring bean with prototype scope: 具有原型范围的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 with missing scope declaration: 缺少范围声明的Spring bean:

package de.scrum_master.app;

import org.springframework.stereotype.Component;

@Component
public class UnscopedBean {}

Spring bean with using different types of auto-wiring: 使用不同类型的自动装配的Spring bean:

This bean uses constructor and setter method wiring. 该bean使用构造函数和setter方法连接。 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;
    }
}

Aspect for static annotation consistency checking: 静态注释一致性检查的方面:

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";
}

Maven POM using AspectJ compiler: 使用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>

Console output for mvn clean package : 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] ------------------------------------------------------------------------
(...)

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. 我认为该示例除了AspectJ语法以外,在某种程度上是不言自明的,但是您可以在AspectJ手册或教程中阅读更多有关该示例的信息。 If you uncomment the @Autowired annotation on the field declaration, you will see even more errors for explicit field assignments. 如果取消注释字段声明中的@Autowired注释,则对于显式字段分配,您还会看到更多错误。 AspectJ cannot match on a mere field declaration (without assignment), though. 但是,AspectJ不能仅在字段声明(没有分配)上匹配。 So whenever your developers rely on field annotations instead of annotated constructors or setter methods, ie you do not have any explicit field assignment in your code, there will be no compilation error. 因此,只要您的开发人员依靠字段注释而不是带注释的构造方法或setter方法,即您的代码中没有任何显式的字段分配,就不会出现编译错误。 You can work around that by matching on getter methods or field read access in your code to indirectly match on fields. 您可以通过在代码中匹配getter方法或字段读取访问以间接匹配字段来解决此问题。 Feel free to ask how to do that if you cannot figure it out by yourself. 如果您自己无法解决,请随时询问该怎么做。

The simplest idea I can think of is using Checkstyle's RegexpSinglelineJava check to ensure 1 and only 1 of @Scope exists per file: http://checkstyle.sourceforge.net/config_regexp.html#RegexpSinglelineJava 我能想到的最简单的想法是使用Checkstyle的RegexpSinglelineJava检查来确保每个文件中只有1个@Scope: http ://checkstyle.sourceforge.net/config_regexp.html#RegexpSinglelineJava

Since you need to check for two annotations, perhaps the RegexpMultiline check will work (would require the annotations ordered consistently): http://checkstyle.sourceforge.net/config_regexp.html#RegexpMultiline 由于您需要检查两个注释,因此RegexpMultiline检查可能会起作用(将需要一致地排列注释): http ://checkstyle.sourceforge.net/config_regexp.html#RegexpMultiline

I recently also needed to verify the scope of @Autowired beans and could not find any suitable out of the box solution. 最近,我还需要验证@Autowired bean的范围,并且找不到任何合适的即用型解决方案。 Thus I've created small project that allows for the validation of bean scopes at runtime. 因此,我创建了一个小型项目 ,该项目允许在运行时验证bean范围。 By default, it allows for the following injections: 默认情况下,它允许进行以下注入:

  • Singletons can be injected into everything 单身人士可以注入一切
  • Everything can be injected into prototypes 一切都可以注入原型
  • AOP proxies can be injected into everything AOP代理可以注入所有内容
  • Everything can be injected into beans of the same scope 一切都可以注入到相同范围的bean中

If you want to allow a bean to be injected into another scope, it needs to be explicitly allowed by using a respective annotation: 如果要允许将bean注入另一个作用域,则需要使用相应的注释明确允许它:

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

If a bean at runtime uses dependencies of a not allowed scope, it can either log it, throw an exception (and thus prevent the creation of the bean) or perform any custom action. 如果bean在运行时使用了不允许范围的依赖关系,则它可以记录它,抛出异常(从而阻止bean的创建)或执行任何自定义操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM