01 | Spring Bean definition common error

Case 1: The Bean definition cannot be implicitly scanned

The Application class is defined as follows:

Package com. Spring. Puzzle. Class1. Example1. Application. The application / / omit the import @ SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

The HelloWorldController code is as follows:

Package com. Spring. Puzzle. Class1. Example1. Controller. The application / / omit the import @ RestController public class HelloWorldController { @RequestMapping(path = "hi", method = RequestMethod.GET) public String hi(){ return "helloworld"; }; }Copy the code

As shown in the package structure, we will find that the Web application fails to recognize the HelloWorldController. In other words, we can’t find the HelloWorldController Bean. Why is that?

Case Study:

To understand why the HelloWorldController failed, you need to understand how it worked before. The key point for SpringBoot is the use of the SpringBootApplication annotation in application.java. This annotation inherits other annotations, as defined below:

@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} {/ / omit non-critical codeCopy the code

SpringBootApplication supports a number of functions, including ComponentScan.

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
Copy the code

The position that ComponentScan scans is specified by the basePackages attribute of the ComponentScan annotation. For details, see the following definitions:

public @interface ComponentScan { /** * Base packages to scan for annotated components. * <p>{@link #value} is an alias for (and mutually exclusive with) this * attribute. * <p>Use {@link #basePackageClasses} for a type-safe alternative to * String-based package names. */ @AliasFor("value") String[] basePackages() default {}; // omit other non-critical code}Copy the code

After debugging, if SpringBootApplication annotation defines ComponentScan, its basePackages are not specified, the package will be declaringClass. In this case, DeclaringClass is Application. The class, so scanning is in fact the package bag, it is in namely com. Spring. The puzzle. Class1. Example1. Application

The problem correction

The real solution here is to explicitly configure @ComponentScan. Specific modification methods are as follows:

@SpringBootApplication @ComponentScan("com.spring.puzzle.class1.example1.controller") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

You can also use @ComponentScans

@SpringBootApplication @ComponentScans(value = { @ComponentScan(value = "com.spring.puzzle.class1.example1.controller") }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

ComponentScan compares ComponentScan with an S, which supports the scanning range of multiple packets.

Case 2: The Bean defined lacks implicit dependencies

The following code snippet appears to be fine, but in fact…

@Service public class ServiceImpl { private String serviceName; public ServiceImpl(String serviceName){ this.serviceName = serviceName; }}Copy the code

ServiceImpl is a Bean because it is marked @service. Additionally, we ServiceImpl explicitly defines a constructor. However, the above code does not always work correctly, and sometimes the following error is reported:

Parameter 0 of constructor in com.spring.puzzle.class1.example2.ServiceImpl required a bean of type 'java.lang.String' that could not be found.
Copy the code

Case Study:

When creating a Bean, it consists of two basic steps: finding the constructor and creating the instance by calling the constructor through reflection. For core code execution, refer to the following code snippet:

// Candidate constructors for autowiring? Constructor<? >[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors ! = null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || ! ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); }Copy the code

Spring will perform determineConstructorsFromBeanPostProcessors method to get the constructor, then through autowireConstructor method with the constructor to create an instance.

To create an instance, the autowireConstructor method needs to know not only which constructor it is, but also which parameters it corresponds to, as can be seen from the name of the method that finally creates the instance, (i.e., ConstructorResolver#instantiate) :

private Object instantiate( String beanName, RootBeanDefinition mbd, Constructor<? > constructorToUse, Object[] argsToUse)Copy the code

What about argsToUse, which stores the construction parameters in the above method? In other words, when we already know the constructor ServiceImpl(String serviceName), how do we determine the value of serviceName to create an instance of ServiceImpl?

In Spring, we cannot create instances directly and explicitly using the new keyword. Spring can only look for dependencies to call as constructor arguments.

To obtain the constructor parameter, see the following code snippet (i.e., ConstructorResolver#autowireConstructor) :

argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
      getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
Copy the code

The createArgumentArray method can be called to build an array of parameters that call the constructor. The final implementation of this method is to get beans from the BeanFactory, as described in the following call:

return this.beanFactory.resolveDependency(
      new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
Copy the code

If you DeBug, you can see more information:

As shown in the figure, the above call is to find the corresponding Bean according to the parameters. In this case, if the corresponding Bean cannot be found, an exception will be thrown, indicating assembly failure.

Problem correction:

Spring’s implicit rule: Define a class as a Bean. If a constructor is explicitly defined, then the Bean will automatically look for the corresponding Bean based on the constructor parameter definition when it is built, and then reflect to create the Bean.

We can directly define a Bean that lets Spring assemble arguments to the ServiceImpl constructor, for example:

@bean public String serviceName(){return "MyServiceName"; }Copy the code

The program runs normally.

Therefore, it is not desirable to use Spring with the idea that beans defined can also be used explicitly in non-Spring situations using the new keyword.

References:

Fu Jian, “50 Common Mistakes in Spring Programming”

The copyright of this article belongs to the author and nuggets jointly, welcome to reprint, but without the consent of the author must retain this statement, and give the original link in a prominent place on the page of the article.