This is the 11th day of my participation in the August More Text Challenge
preface
We in the development of springboot project, create a good Springboot project can be started by launching the class directly, run a Web project, very convenient and simple, It’s not like we used to run a Web project with Spring+Spring MVC and have to configure various package scans and Tomcat to get it started
I split the app into parent+common+ Component +app,
- Parent is a simple POM file that holds some of the project’s common dependencies
- Common is a SpringBoot project with no boot classes and houses the core common code of the project
- Component Various function service modules, which are implemented by direct reference to plug and remove
- App is a real application project that contains a SpringBoot boot class that provides various real functions.
Kmall-admin and kmall-API are actual application projects containing a SpringBoot boot class
However, when I started the project, I found that some modules were not successfully injected and the configuration classes did not take effect. That is, SpringBoot does not scan these files
Scenario analysis
SpringBoot default scanning mechanism
The default package scanning mechanism of SpringBoot is to scan all files under the current package and its sub-packages starting from the package where the startup class resides.
Because at first I start class package called: cn. Soboys. Kmall. Admin. WebApplication, and other project documents package names are all cn. Soboys. Kmall. * XxxClass, so the other modules are refer to the following files could not be scanned
SpringBoot starts scanning annotations for the class
There are three ways to configure the scan package path on the SpringBoot boot class. Recently, I saw an application using all three annotations, the code is as follows:
@SpringBootApplication(scanBasePackages ={"a","b"})
@ComponentScan(basePackages = {"a","b","c"})
@MapperScan({"XXX"})
public class XXApplication extends SpringBootServletInitializer
}
Copy the code
So, the question is: what is the priority of these three annotations in SpringBoot, and is there any difference between the first and second annotations
SpringBootApplication annotations
This is SpringBoot annotations, essentially three Spring annotations and look at the source can be known
//
// 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(a) default true;
}
Copy the code
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
By default, it scans the startup class package and all its subpackages, but does not include other directories of third-party JAR packages. You can reset the package path by using the scanBasePackages property
ComponentScan annotations
This is the Spring framework annotation that specifies the component scan path. If you use this annotation, its value must contain all the paths that need to be scanned throughout the project. Because it overrides the default scan path of SpringBootApplication, it becomes invalidated.
There are two kinds of failure manifestations:
-
If only one value is included for ComponentScan, SpringBootApplication takes effect, and the ComponentScan annotation fails:
-
If ComponentScan specifies multiple specific subdirectories, Spring bootApplication is disabled and only scans annotations in the specified ComponentScan directory. If you happen to have Controller classes outside the directory, unfortunately, those controllers will not be accessible.
MapperScan annotations
- So here we go again
@Mapper
annotations
Add @mapper annotation directly to Mapper class. This method requires that every Mapper class need to add this annotation
- Through the use of
@MapperScan
You can specify the path to the package of the Mapper class to scan
MyBatis will encapsulate all Mapper classes in the specified directory into MyBatis BaseMapper class, generate the corresponding XxxMapper proxy interface implementation class and then inject it into the Spring container, without additional annotations, you can complete the injection.
Annotate multiple packages with @mapperscan
@SpringBootApplication
@MapperScan({"com.kfit.demo","com.kfit.user"})
public class App {
public static void main(String[] args) { SpringApplication.run(App.class, args); }}Copy the code
If the Mapper class is not under a package or subpackage that the Spring Boot main program can scan, you can configure it as follows
@SpringBootApplication
@MapperScan({"com.kfit.*.mapper","org.kfit.*.mapper"})
public class App {
public static void main(String[] args) { SpringApplication.run(App.class, args); }}Copy the code
To solve the scene
So after analyzing all the above notes, we have two solutions:
- through
@SpringBootApplication
The specifiedscanBasePackages
Property to reset the scan packet path
@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();
}
/** * prints all loaded beans */
public static void displayAllBeans(a) {
String[] allBeanNames = applicationContext.getBeanDefinitionNames();
for(String beanName : allBeanNames) { System.out.println(beanName); }}}Copy the code
- through
@ComponentScan
The specifiedbasePackages
Property specifies the component scan path
@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();
}
/** * prints all loaded beans */
public static void displayAllBeans(a) {
String[] allBeanNames = applicationContext.getBeanDefinitionNames();
for(String beanName : allBeanNames) { System.out.println(beanName); }}}Copy the code
Of course, we can see that the attribute nameGenerator is also specified in the scan to solve the problem of scanning injection conflicts in multiple modules, multiple package names and the same class name
Spring provides two beanName generation strategy, based on the annotation of sprong – boot default AnnotationBeanNameGenerator, it generates beanName strategy is to take the class name as beanName (not a fully qualified class name). Thus, if you have the same class name under different package structures, you are bound to have conflicts
The solution We can write a class implements our org. Springframework. Beans. Factory. Support. BeanNameGeneraot interface
AnnotationBeanNameGenerator. Redefining beanName generation strategy, inheritance, rewrite generateBeanName
Also solve the problem of the same mapper bean name under different packages of Mybatis
public class UniqueNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
// Fully qualified class name
String beanName = definition.getBeanClassName();
returnbeanName; }}Copy the code
public class UniqueNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
// If value is set, use value; if not, use the full class name
if (definition instanceof AnnotatedBeanDefinition) {
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}else{
// Fully qualified class name
beanName = definition.getBeanClassName();
returnbeanName; }}// Use the default class name
returnbuildDefaultBeanName(definition, registry); }}Copy the code
This is the qualified universal name, that is, the package name + the class name
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");
// Split class full path
String[] packages = beanClassName.split("\ \.");
StringBuilder beanName = new StringBuilder();
// Take the first letter of the package name of the class and add the class name as the last bean name
for (int i = 0; i < packages.length - 1; i++) {
beanName.append(packages[i].toLowerCase().charAt(0));
}
beanName.append(packages[packages.length - 1]);
returnbeanName.toString(); }}Copy the code
This takes the first letter of the package name of the class and then the class name as the last bean name
- Resolve this by specifying the scan name of the conflicting class name separately
Specify value when scanning the @service annotation or the @Controller annotation on two classes of the same name,
- @ Primary annotations
This annotation is intended to address the fact that instances with this annotation are selected when more than one bean meets the injection criteria
References:
- Usage and conflict principles of scanning annotations
- The same class at