1.10. Classpath Scanning and Managed Components

Most of the examples in this chapter use XML configuration. The previous section (annotation-based container configuration) demonstrated how to configure beans through annotations. Although this is an annotation configuration, the basic “bean” definition is also explicitly defined in the XML file, and annotations only drive dependency injection.

This section describes implicitly detecting candidate Spring components by scanning classpath. A candidate component is a class that meets the filter criteria and has the corresponding bean Definition registered in the container. This eliminates the need to use XML to perform bean registration. Instead, you can use annotations (such as @Component), AspectJ-type expressions, or your own custom filter criteria to select which classes have registered bean definitions with the container.

Since Spring 3.0, many of the features provided by the Spring JavaConfig project are part of the core Spring framework. This allows you to define beans using Java instead of traditional XML files. Such as @Configuration, @Bean, @import and @dependsonCopy the code
1.10.1. @Component and stereotype annotations

The @repository annotation is a tag for any class (also known as a data access object or DAO) that satisfies the Repository role or stereotype. The markup can also be used to automatically translate exceptions, encapsulating data access exceptions thrown in annotated classes as Spring’s data access exception types.

Spring provides further stereotype annotations :@Component, @Service, and @Controller. @Component is a generic prototype for any Spring-managed Component. @Repository, @Service, and @Controller are refinements of @Component for more specific use cases (in the persistence, Service, and presentation layers, respectively).

Therefore, you can annotate Component classes with @Component, but by annotating them with @Repository, @Service, or @Controller, your classes are better suited for tooling or aspect association. For example, these stereotype annotations are ideal targets for AOP pointcuts.

@Repository, @Service, and @Controller can also come with additional semantics in future versions of the Spring framework, just like the @Repository exception translation. Therefore, if you have a choice between using @Component or @service for your Service layer, @Service is clearly the better choice. Similarly, as mentioned earlier, @Repository is already supported as a marker for automatic exception transformation in the persistence layer.

1.10.2. Use meta-annotations and composite annotations

Many of the annotations provided by Spring can be used as meta-annotations in your own code. A meta-annotation is an annotation that can be applied to another annotation. For example, there is a meta-annotation @Component on the @service annotation, as shown in the following example:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { // ... } // The presence of @Component causes Spring to treat @Service as @Component.Copy the code

You can also combine meta-annotations to create “composite annotations.” For example, the @restController annotation in Spring MVC consists of @Controller and makes up @responseBody.

In addition, composite annotations have the option of redeclaring properties from meta-annotations to allow customization. This is especially useful when you only want to expose a subset of meta-annotation attributes. For example, Spring’s @SessionScope annotation hardcodes the @Scope value attribute as session, but still allows you to customize the @Scope proxyMode. The following listing shows the definition of the SessionScope annotation:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}
Copy the code

Then, you @sessionScope can use the following without declaring proxyMode:

@Service
@SessionScope
public class SessionScopedService {
    // ...
}
Copy the code

You can also override the value proxyMode, as shown in the following example:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}
Copy the code
1.10.3. Automatically detect classes and register Bean Definitions

Spring can automatically detect the stereotype class and use it to register the corresponding BeanDefinition instance ApplicationContext. For example, the following two categories are eligible for this automatic detection:

@Service public class SimpleMovieLister { private MovieFinder movieFinder; public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; }}Copy the code
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}
Copy the code

To automatically detect these classes and register the corresponding beans, you need to add @ComponentScan to the @Configuration class. The basePackages property is the common parent package for both classes. (Alternatively, you can specify a comma -, semicolon -, or space-separated list of parent packages for each class.)

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
Copy the code

The following uses XML alternatives:

<? The XML version = "1.0" encoding = "utf-8"? > <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.example"/>  </beans>Copy the code
Use the function <context:component-scan> implicitly enabled <context:annotation-config>. <context:annotation-config> is usually used without including the element <context:component-scan>.Copy the code

In addition, when you use the component – scan elements, AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor implicitly included. This means that the two components are automatically detected and wired together, all without requiring any of the bean configuration metadata provided in the XML.

You can include the annotation – the config and the value of the attribute to disable the register AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor false. As follows:

	<context:component-scan base-package="org.springframework.example" annotation-config="false"/>

Copy the code
1.10.4. Use filters to customize scans

By default, only classes annotated with @Component, @Repository, @Service, @Controller, @Configuration, or custom annotations themselves annotated with @Component are detected.

However, you can modify and extend this behavior by applying custom filters. For example, modify the includeFilters or excludeFilters attribute of the @ComponentScan annotation (or context:component-scan element context:include-filter or context:exclude-fi) Lter child element).

The following table describes the filtering options:

Table 5. Filter types

Filter type For example, describe
annotation (default) org.example.SomeAnnotation The current annotation that exists in the target component’s annotation or meta-annotation
assignable org.example.SomeClass The target component inherits or implements from a class (or interface)
aspectj org.example.. *Service+ The AspectJ-type expression to match for the target component.
regex org.example.Default.* The regular expression to match the class name of the target component.
custom org.example.MyTypeFilter Org. Springframework. Core. Type. The TypeFilter the custom implementations of the interface.
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}
Copy the code

The following listing shows the equivalent XML:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>
Copy the code
You can also disable the default filters by setting the useDefaultFilters=false annotation or by modifying the use-default-filters="false" attribute of the <component-scan/> element. This effectively disables automatic class detection of annotations or meta-annotations (@Component, @Repository, @Service, @Controller, @RestController, or @Configuration).Copy the code
1.10.5. Define the Bean metadata in the component

Spring components can also provide bean definition metadata to the container. You can do this using the same @Bean annotation that defines the bean metadata in the @Configuration annotation class.

The following example shows how to do this:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}
Copy the code

The previous class is a Spring component with application-specific code in its doWork() method. However, it also provides a bean definition with a factory method that references the method publicInstance(). The @bean annotation identifies factory methods and other bean-definition properties, such as Qualifier values identified through the @qualifier annotation. Other method-level annotations available include @scope, @lazy, and custom Qualifier annotations.

@lazy In addition to the role used for component initialization, you can also place the @lazy annotation @inject on an injection point marked @Autowired or. In this case, it results in the injection of a lazy resolution proxy.Copy the code

As mentioned earlier, auto-injected fields and methods are supported, and additional support for @bean methods is automatically supported. The following example shows how to do this:

@Component public class FactoryMethodComponent { private static int i; @Bean @Qualifier("public") public TestBean publicInstance() { return new TestBean("publicInstance"); } // The value of privateInstance.age is automatically injected // beans with Qualifier values public are injected @bean protected TestBean protectedInstance( @Qualifier("public") TestBean spouse, @Value("#{privateInstance.age}") String country) { TestBean tb = new TestBean("protectedInstance", 1); tb.setSpouse(spouse); tb.setCountry(country); return tb; } @Bean private TestBean privateInstance() { return new TestBean("privateInstance", i++); } @Bean @RequestScope public TestBean requestScopedInstance() { return new TestBean("requestScopedInstance", 3); }}Copy the code

In the Spring Framework 4.3, you can also declare a factory method parameter of type InjectionPoint (or its more specific subclass :DependencyDescriptor) to access the request injection that triggers the creation of the current bean. You can see which bean is being instantiated as a property of the bean (e.g., the name of the property when instantiated, the class in which it was instantiated, etc.).

Note that this only applies to the actual creation of the bean instance and is not triggered when the existing instance is injected. Therefore, this feature makes the most sense for prototype-scoped beans. For other scopes, factory methods can only see injection points that trigger the creation of a new bean instance within a given scope (for example, dependencies that trigger the creation of a lazy singleton bean). You can use the provided injection point metadata with caution in such scenarios.

The following example shows how InjectionPoint can be used:

@Component public class FactoryMethodComponent { @Bean @Scope("prototype") public TestBean prototypeInstance(InjectionPoint injectionPoint) { return new TestBean("prototypeInstance for " + injectionPoint.getMember()); }}Copy the code

The @Bean methods in regular Spring components are handled differently than their counterparts in the Spring @Configuration class. The difference is that the @Configuration class is enhanced by CGLIB. The @Component class is not enhanced by CGLIB to intercept method and field calls.

You can declare @Bean methods static, allowing them to be invoked without creating an instance of the configuration class that contains them. This makes particular sense when defining post-processor beans (for example, BeanFactoryPostProcessor or BeanPostProcessor types), because such beans are initialized early in the lifecycle of the container and should avoid triggering other parts of the configuration at this point. The container will never intercept calls to static @bean methods, even in the @Configuration class (as described earlier in this section), due to technical limitations :CGLIB subclassing can only override non-static methods. The visibility of the @Bean method in the Java language has no direct impact on the Bean definitions generated in the Spring container. You are free to declare your factory methods in the non-@Configuration class, and you can declare static methods anywhere. However, the normal @bean methods in the @Configuration class need to be overridden -- that is, they cannot be declared private or final. The @bean method can also be found in the base class of a given component or configuration class, or in Java 8 in the default method declared in the interface implemented by the component or configuration class. This provides great flexibility to combine complex configuration arrangements, and even multiple inheritance is possible under the default approach of java8. interface OneClass{ default void printOne(){ System.out.println("print one"); } } interface TwoClass{ default void printTwo(){ System.out.println("print two"); } } public class SonClass implements OneClass, TwoClass { public static void main(String[] args) { SonClass son = new SonClass(); son.printOne(); son.printTwo(); }} Each interface defines a default method. So SonClass classes can call methods from these two interfaces. It's like multiple inheritance. Finally, a class might reserve multiple @Bean methods for the same bean as an arrangement of factory methods to be used at run time based on available dependencies. This is the same algorithm used to select constructors or factory methods in other configuration scenarios: the variables that satisfy the most dependencies are selected at build time, similar to a container choosing between multiple @Autowired constructors. @bean public ServiceTwo ServiceTwo(ectionPoint InjectionPoint){return new ServiceTwo(); } @Bean public ServiceTwo serviceTwo(InjectionPoint injectionPoint, CusService cusService){ return new ServiceTwo(); } Only one of these beans will be instantiated for useCopy the code
1.10.6. Name automatically detected components

When a component is automatically detected as part of the scanning process, its bean name is generated by the scanner-aware BeanNameGenerator policy.

By default, some Spring annotations (@Component, @repository, @Service, and @Controller) contain a property named value that provides a name for the corresponding bean definition.

If the value value does not contain any name value, the default bean name generator returns a string with the first letter of the class name in lower case as beanName. For example, if the following component classes were detected, the names would be myMovieLister and movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}

@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
Copy the code

If you do not want to rely on the default Bean naming policy, you can provide a custom Bean naming policy. First, implement the BeanNameGenerator interface and make sure to include the default no-ARg constructor. You then provide fully qualified class names when configuring the scanner, as shown in the sample annotations and Bean definitions below.

@Configuration @ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) public class AppConfig { // ... } or < beans > < context: component - scan base - package = "org. Example" name - the generator = "org. Example. MyNameGenerator" / > < / beans >Copy the code
If you encounter naming conflicts because multiple automatically detected components have the same unqualified class name (that is, classes with the same name but in different packages), you may need to configure a BeanNameGenerator with the class's full path as a beanName. In the Spring Framework 5.2.3 requires, in the package org. Springframework. FullyQualifiedAnnotationBeanNameGenerator in context can be used for this. public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator { @Override protected String buildDefaultBeanName(BeanDefinition definition) { String beanClassName = definition.getBeanClassName(); Assert.state(beanClassName ! = null, "No bean class name set"); return beanClassName; }}Copy the code

As a general rule, consider using the annotation to specify the name when other components might explicitly reference it. On the other hand, as long as the container is responsible for the connection, the automatically generated name is sufficient.

1.10.7. Provides scope for automatically detecting components

In general, as with spring-managed components, the default and most common scope for automatically detected components is Singleton. However, sometimes you need additional scopes that the @scope annotation can specify. You can provide the name of the scope in the annotation, as shown in the following example:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
Copy the code
The @Scope annotation is only used on concrete bean classes (for any @Component annotations) or factory methods (for @Bean methods).Copy the code

In contrast to XML bean definitions, no bean defines the concept of inheritance, and the inheritance hierarchy at the class level is independent of metadata purposes. For more information on Web-specific scopes, see the fifth article in this series.

To provide a custom strategy for Scope parsing rather than relying on an annotation-based approach, you can implement the Scopemetadaresolver interface. Make sure to include the default no-argument constructor.

You can then provide fully qualified class names when configuring the scanner, as shown in the following annotation and bean definition example:

@Configuration @ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) public class AppConfig { // ... } or < beans > < context: component - scan base - package = "org. Example" scope - the resolver = "org. Example. MyScopeResolver" / > < / beans >Copy the code

When you use some non-singleton scopes, you may need to generate proxies for scope objects. Again, this is due to scoping issues, such as the need to use a proxy for a prototype-reference bean whose scope is a singleton bean; otherwise, the singleton bean will always hold an instance of Prototype. To do this, there is a scoped proxy attribute on the Component-scan element. The three possible values are no, interface, and targetClass. For example, the following configuration leads to standard JDK dynamic proxies:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
或者
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
Copy the code
1.10.8. Generate indexes for candidate components

Although classpath scanning is very fast, you can improve startup performance of large applications by creating a static candidate list at compile time. All classes that need to be registered as beans are filtered out at compile time and used directly at startup. In this mode, all modules that are the target of component scanning must use this mechanism.

Your existing @ComponentScan or when ApplicationContext detects such an index, it automatically uses it instead of scanning the classpath.Copy the code

To generate an index, add additional dependencies to each module that contains the target component of the component scan instruction. The following example shows how to do this using Maven:

<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-indexer</artifactId> < version > 5.2.9. RELEASE < / version > < optional > true < / optional > < / dependency > < / dependencies >Copy the code

For Gradle 4.5 and earlier, you should declare dependencies in the compileOnly configuration, as shown in the following example:

Dependencies {compileOnly "org. Springframework: spring - the context - the indexer: 5.2.9. RELEASE"}Copy the code

For Gradle 4.6 and later, dependencies should be declared in annotationProcessor configuration, as shown in the following example:

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
Copy the code

This process generates a file named Conjugate IN/Spring.components.components.jar. As follows:

org.springframework.example.Cancel=org.springframework.stereotype.Component
org.springframework.example.Transfer=org.springframework.stereotype.Component
Copy the code

When this pattern is used in the IDE, spring-Context-Indexer must register it as an annotation handler to ensure that the index is up to date when the candidate component is updated.

The index is automatically enabled when it is found in the classpath.

If the index is partially available for some libraries (or use cases) but cannot be built for the entire application, you can return to the regular classpath arrangement by setting Spring.index.ignore to true.