SpringBoot パッケージのスキャン マルチモジュール マルチパッケージ名のスキャンと類似名のスキャンの競合解決

序文

プロジェクトを開発するspringbootとき、SpringBoot プロジェクトを作成した後、スタートアップ クラスを介して直接開始できます。Web プロジェクトの実行は非常に便利で簡単です。Web プロジェクトを実行して開始するのとは異なり、さまざまな設定を行う必要がありますSpring+SpringMvc。パッケージをスキャンしてtomcat開始します。

アプリケーションを親+共通+コンポーネント+アプリのパターンに分割しましたが、

  1. 親は、プロジェクトのいくつかのパブリック依存関係を保存する単純な pom ファイルです。
  2. common はスタートアップ クラスのない SpringBoot プロジェクトで、プロジェクトのコア パブリック コードが格納されます。
  3. コンポーネント 各種コンポーネント機能サービスモジュールを利用する場合、プラグインメソッドを直接参照して実現します。
  4. app は SpringBoot スタートアップ クラスを含む実際のアプリケーション プロジェクトであり、さまざまな実用的な機能を提供します。

その中にはkmall-adminkmall-apiSpringBoot スタートアップ クラスを含む実際のアプリケーション プロジェクトがあります。

しかし、プロジェクトを開始してみると、一部のモジュールが正常に挿入されておらず、構成クラスが有効になっていないことがわかりました。つまり、SpringBoot はこれらのファイルをスキャンしませんでした。

シーン分析

SpringBoot のデフォルトのスキャンメカニズム

SpringBoot のデフォルトのパッケージ スキャン メカニズムは次のとおりです。启动类パッケージから開始して、現在のパッケージとそのサブパッケージの下にあるすべてのファイルをスキャンします。

スタートアップ クラスのパッケージ名は でありcn.soboys.kmall.admin.WebApplication、他のプロジェクト ファイルのパッケージ名はすべて cn.so であるためboys.kmall.*.XxxClass、他のモジュールが参照されている場合、次のファイルをスキャンして挿入することはできません。

SpringBoot スタートアップ クラスのアノテーションのスキャン

SpringBoot スタートアップ クラスでは、スキャン パッケージのパスを構成する方法が 3 つあります。最近、アプリケーションで 3 つのアノテーションがすべて使用されていることがわかりました。コードは次のとおりです。

@SpringBootApplication(scanBasePackages ={
    
    "a","b"})
@ComponentScan(basePackages = {
    
    "a","b","c"})
@MapperScan({
    
    "XXX"})
public class XXApplication extends SpringBootServletInitializer 
}

そこで、次のような質問が生じます。SpringBoot では、これら 3 つのアノテーションの優先順位は何ですか? 最初と 2 番目に違いはありますか?

SpringBootApplication アノテーション

これは SpringBoot のアノテーションであり、基本的に 3 つの Spring アノテーションであり、ソース コードから確認できます。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {
    
    @Filter(
    type = FilterType.CUSTOM,
    classes = {
    
    TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {
    
    AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    
    
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {
    
    };

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {
    
    };

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {
    
    };

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {
    
    };

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

  1. @SpringBootConfiguration
  2. @EnableAutoConfiguration
  3. @ComponentScan

デフォルトでは、スタートアップ クラスが配置されているパッケージとそのすべてのサブパッケージがスキャンされますが、サードパーティの jar パッケージの他のディレクトリは含まれません。スキャン パッケージのパスは、scanBasePackages 属性を通じてリセットできます。

ComponentScan アノテーション

これはSpring、コンポーネントのスキャン パスを指定するために使用されるフレームワークのアノテーションです。このアノテーションが使用される場合、その値には、プロジェクト全体でスキャンする必要があるすべてのパスが含まれている必要があります。これは覆盖 SpringBootApplicationデフォルトのスキャンパスとなり、失敗するためです。

失敗には 2 つのタイプがあります。

  1. ComponentScan に値が 1 つだけ含まれており、それがデフォルトのスタートアップ クラス ディレクトリである場合、SpringBootApplication が有効になり、ComponentScan アノテーションは失敗し、エラーが報告されます。

  2. ComponentScan が複数の特定のサブディレクトリを指定した場合、SpringBootApplication はこの時点で失敗し、Spring は ComponentScan で指定されたディレクトリ内の注釈のみをスキャンします。ディレクトリの外にコントローラー クラスが存在する場合、残念ながらそれらのコントローラーにはアクセスできません。

MapperScan 注釈

  1. これには@Mapperアノテーションも関係します。アノテーションを
    直接追加する方法です。この方法では、このアノテーションを各自で追加する必要があり、面倒です。Mapper类@Mappermapper类

  2. これが@MapperScanアノテーションであるスキャン対象のMapperクラスを指定できるパッケージのパスを使用することで、指定したディレクトリ内のすべてのMapperクラスがMyBatisのBaseMapperクラスにカプセル化され、対応するXxxMapperプロキシインターフェース実装クラスが生成されます。その後、追加のアノテーションなしで Spring コンテナーに注入され、注入を完了できます。
    MyBatis

//使用@MapperScan注解多个包

@SpringBootApplication  
@MapperScan({
    
    "com.kfit.demo","com.kfit.user"})  
public class App {
    
      
    public static void main(String[] args) {
    
      
       SpringApplication.run(App.class, args);  
    }  
} 

マッパー クラスが Spring Boot メイン プログラムがスキャンできるパッケージまたはサブパッケージの下にない場合は、次の方法で構成できます。

@SpringBootApplication  
@MapperScan({
    
    "com.kfit.*.mapper","org.kfit.*.mapper"})  
public class App {
    
      
    public static void main(String[] args) {
    
      
       SpringApplication.run(App.class, args);  
    }  
} 

シーンの解像度

したがって、上記のすべてのアノテーションを分析した後、2 つの解決策が得られます。

  1. スキャン パッケージのパスは、プロパティ@SpringBootApplicationを指定することでリセットできます。scanBasePackages
@SpringBootApplication(scanBasePackages = {
    
    "cn.soboys.kmall"},nameGenerator = UniqueNameGenerator.class)
@MapperScan(value = {
    
    "cn.soboys.kmall.mapper","cn.soboys.kmall.sys.mapper",
        "cn.soboys.kmall.security.mapper","cn.soboys.kmall.monitor.mapper"},nameGenerator = UniqueNameGenerator.class)
public class WebApplication {
    
    
    private static ApplicationContext applicationContext;

    public static void main(String[] args) {
    
    
        applicationContext =
                SpringApplication.run(WebApplication.class, args);
        //displayAllBeans();
    }


    /**
     * 打印所以装载的bean
     */
    public static void displayAllBeans() {
    
    
        String[] allBeanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : allBeanNames) {
    
    
            System.out.println(beanName);
        }
    }
}
  1. @ComponentScan属性を指定しbasePackagesコンポーネントのスキャンパスを指定します
@ComponentScan(basePackages =  {
    
    "cn.soboys.kmall"},nameGenerator = UniqueNameGenerator.class)
@MapperScan(value = {
    
    "cn.soboys.kmall.mapper","cn.soboys.kmall.sys.mapper",
        "cn.soboys.kmall.security.mapper","cn.soboys.kmall.monitor.mapper"},nameGenerator = UniqueNameGenerator.class)
public class WebApplication {
    
    
    private static ApplicationContext applicationContext;

    public static void main(String[] args) {
    
    
        applicationContext =
                SpringApplication.run(WebApplication.class, args);
        //displayAllBeans();
    }


    /**
     * 打印所以装载的bean
     */
    public static void displayAllBeans() {
    
    
        String[] allBeanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : allBeanNames) {
    
    
            System.out.println(beanName);
        }
    }
}

もちろん、nameGeneratorマルチモジュール、マルチパッケージ名、およびマルチパッケージ名の問題を解決するために、スキャンでも属性が指定されていることがわかります。相同类名,扫描注入冲突问题

Spring には 2 つのbeanName生成戦略が用意されています。アノテーションベースの Spring-boot は、デフォルトで AnnotationBeanNameGenerator を使用します。BeanName を生成する戦略は、現在のクラス名 (完全修飾クラス名ではない) を beanName として取得します。したがって、同じクラス名が異なるパッケージ構造の下にある場合、間違いなく競合が発生します。

解決策として、クラスの実装を自分で書くことができます。org.springframework.beans.factory.support.BeanNameGeneraot接口

.BeanName 生成戦略を再定義し、AnnotationBeanNameGenerator を継承し、generateBeanName を書き換えます

また、mybatis の異なるパッケージで同じ名前のマッパー Bean 名が重複する問題も解決します。

public class UniqueNameGenerator extends AnnotationBeanNameGenerator {
    
    
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    
    

        //全限定类名

        String beanName = definition.getBeanClassName();

        return beanName;

    }

}
public class UniqueNameGenerator extends AnnotationBeanNameGenerator {
    
    

    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    
    

        //如果有设置了value,则用value,如果没有则是用全类名
        if (definition instanceof AnnotatedBeanDefinition) {
    
    
            String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
            if (StringUtils.hasText(beanName)) {
    
    
                // Explicit bean name found.
                return beanName;
            }else{
    
    
                //全限定类名
                beanName = definition.getBeanClassName();
                return beanName;
            }
        }

        // 使用默认类名
        return buildDefaultBeanName(definition, registry);
    }
}

これは完全修飾クラス名です。つまり、包名+类名

package com;
 
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
 
@Component("myNameGenerator")
public class MyNameGenerator extends AnnotationBeanNameGenerator {
    
    
    @Override
    protected String buildDefaultBeanName(BeanDefinition definition) {
    
    
        String beanClassName = definition.getBeanClassName();
        Assert.state(beanClassName != null, "No bean class name set");
        //分割类全路径
        String[] packages = beanClassName.split("\\.");
        StringBuilder beanName = new StringBuilder();
        //取类的包名的首字母小写再加上类名作为最后的bean名
        for (int i = 0; i < packages.length - 1; i++) {
    
    
            beanName.append(packages[i].toLowerCase().charAt(0));
        }
        beanName.append(packages[packages.length - 1]);
        return beanName.toString();
    }
}

これはクラスを取得し包名的首字母小写、それを类名最終的な Bean 名として追加します。

  1. 競合するクラス名のスキャン名を別途指定することで解決

同じ名前の 2 つのクラスにアノテーション@Serviceを付けるとき、または@controllerアノテーションをスキャンするときに値を指定します。

  1. @プライマリアノテーション

このアノテーションは、複数の Bean がインジェクション条件を満たした場合に、このアノテーションを持つインスタンスが選択される問題を解決するためのものです

参考文献:

  1. スキャンアノテーションの使用法と競合原則
  2. 同名のクラスラッシュ

おすすめ

転載: blog.csdn.net/u011738045/article/details/119862322