In-depth study of Spring component registration

Students who have been in contact with Spring must have heard of IOC. In traditional Java programming, when we need to use an object, we all actively display the creation of an object instance (new). After using Spring, there is no need to do this, because Spring will help us to automatically inject certain objects where we need to use them, without us having to create them ourselves. This mode is commonly known as Inversion of Control, or IOC (Inversion of Control). So where does Spring get the objects we need? In fact, Spring provides us with an IOC container, which manages all the objects we need, and component registration is to tell Spring which classes need to be managed by the IOC container.

Some details of component registration are mainly recorded here.

Register components via @Bean

In earlier versions of Spring, we all registered components to and from the IOC container through XML. The following code is certainly not unfamiliar:


// 返回 IOC 容器,基于 XML配置,传入配置文件的位置
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xxx.xml");
User user = (User) applicationContext.getBean("user");

After Spring 4, it is recommended that we use Java Config to register components.

To demonstrate, we build a simple Spring Boot application through http://start.spring.io/ , and then introduce Lombok dependencies (the editor also needs to install the Lombok plug-in):

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

Then create a User class:

@ToString
@AllArgsConstructor
@Data
public class User {
    private String name;
    private Integer age;
}

Then create a configuration class, and @Beanregister the User class through annotations in it :

@Configuration
public class WebConfig {
    @Bean()
    public User user() {
        return new User("mrbird", 18);
    }
}

Through the @Beanannotation, we registered a name with the IOC container user(the Bean name defaults to the method name, and we can also @Bean("myUser")specify the component name as myUser).  

After the component is registered, we test to get this component from the IOC container. Write the following code in the Spring Boot entry class:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
    	SpringApplication.run(DemoApplication.class, args);

        // 返回 IOC 容器,使用注解配置,传入配置类
        ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

Because we register components through annotations, we need to use them AnnotationConfigApplicationContextto get the corresponding IOC container, and the input parameter is the configuration class.

Start the project and look at the console output

QQæªå¾20181207155127.png

The component registration is successful.QQ screenshot 20181207155127.png

We change the name of the component myUser, and then see if the User type component is called in the IOC container myUser:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);

        ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        // 查看 User 这个类在 Spring 容器中叫啥玩意
        String[] beanNames = context.getBeanNamesForType(User.class);
        Arrays.stream(beanNames).forEach(System.out::println);
    }
}

Start the project and observe the console output:

QQæªå¾20181207155718.png

Scan with @ComponentScan

When using XML configuration component scanning, we are all configured like this:

<context:component-scan base-package=""></context:component-scan>

Which base-packagespecifies the path of the scan. All the paths @Controller, @Service, @Repositoryand @Componentcomments will be included in the class labels IOC container.

Now that we are out of the XML configuration, we can use @ComponentScanannotations to scan components and register them.

Before using the @ComponentScanscan, we first create a Controller, a Service, and a Dao, and mark the corresponding annotations.

Then modify the configuration class:

@Configuration
@ComponentScan("cc.mrbird.demo")
public class WebConfig {

    // @Bean("myUser")
    // public User user() {
    //     return new User("mrbird", 18);
    // }
}

In the configuration class, we have @ComponentScan("cc.mrbird.demo")configured the scan path and commented out the User component registration. Instead, @Componentannotated the User class :

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Data
@Component
public class User {
    private String name;
    private Integer age;
}

It is worth noting that we cannot include Spring Boot's entry class in the scanning range, otherwise the project will start up with an error.

Next, let's see if these components are included in the annotation-based IOC container:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);

        ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        // 查看基于注解的 IOC容器中所有组件名称
        String[] beanNames = context.getBeanDefinitionNames();
        Arrays.stream(beanNames).forEach(System.out::println);
    }
}

Start the project and observe the console:

It can be seen that the component has been successfully scanned in, and the name defaults to the lowercase first letter of the class name.

Here, the configuration class WebConfig has also been scanned and registered, and the @Configurationreason will be found by viewing the source code:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

Specify scanning strategy

@ComponentScanThe annotation allows us to specify the scanning strategy, that is, specify which ones are scanned and which ones are not scanned. These two attributes can be found by viewing the source code:

/**
 * Specifies which types are eligible for component scanning.
 * <p>Further narrows the set of candidate components from everything in {@link #basePackages}
 * to everything in the base packages that matches the given filter or filters.
 * <p>Note that these filters will be applied in addition to the default filters, if specified.
 * Any type under the specified base packages which matches a given filter will be included,
 * even if it does not match the default filters (i.e. is not annotated with {@code @Component}).
 * @see #resourcePattern()
 * @see #useDefaultFilters()
 */
Filter[] includeFilters() default {};

/**
 * Specifies which types are not eligible for component scanning.
 * @see #resourcePattern
 */
Filter[] excludeFilters() default {};

Which Filteris also a note:

/**
 * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
 * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {

    FilterType type() default FilterType.ANNOTATION;

    @AliasFor("classes")
    Class<?>[] value() default {};

    @AliasFor("value")
    Class<?>[] classes() default {};
    String[] pattern() default {};

Next we use excludeFiltersto exclude scans of some components:

@Configuration
@ComponentScan(value = "cc.mrbird.demo",
        excludeFilters = {
                @Filter(type = FilterType.ANNOTATION,
                        classes = {Controller.class, Repository.class}),
                @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = User.class)
        })
public class WebConfig {

}

Above we specified two rules to exclude scanning:

  1. Exclude based on annotations ( type = FilterType.ANNOTATION), the types of these annotations are classes = {Controller.class, Repository.class}. That is Controller, Repositorythe classes marked with annotations are no longer included in the IOC container.

  2. According to the specified type class exclusion ( type = FilterType.ASSIGNABLE_TYPE), the exclusion type is User.class, its subclasses and implementation classes will all be excluded.

Start the project and observe the console:

It can be seen that the exclusion was successful.

In addition to the above two commonly used rules, we can also use other rules, view the FilterTypesource code:

public enum FilterType {
    /**
     * Filter candidates marked with a given annotation.
     *
     * @see org.springframework.core.type.filter.AnnotationTypeFilter
     */
    ANNOTATION,

    /**
     * Filter candidates assignable to a given type.
     *
     * @see org.springframework.core.type.filter.AssignableTypeFilter
     */
    ASSIGNABLE_TYPE,

    /**
     * Filter candidates matching a given AspectJ type pattern expression.
     *
     * @see org.springframework.core.type.filter.AspectJTypeFilter
     */
    ASPECTJ,

    /**
     * Filter candidates matching a given regex pattern.
     *
     * @see org.springframework.core.type.filter.RegexPatternTypeFilter
     */
    REGEX,

    /**
     * Filter candidates using a given custom
     * {@link org.springframework.core.type.filter.TypeFilter} implementation.
     */
    CUSTOM
}

As you can see, we can also specify scanning strategies through ASPECTJexpressions, REGEXregular expressions and CUSTOMcustom rules (described in detail below).

includeFiltersThe role and the excludeFiltersopposite, it specifies which components need to be scanned:

@Configuration
@ComponentScan(value = "cc.mrbird.demo",
        includeFilters = {
                @Filter(type = FilterType.ANNOTATION, classes = Service.class)
        }, useDefaultFilters = false)
public class WebConfig {

}

The above configuration will only Controllerbe included IOC container, and the need useDefaultFilters = falseto close the Spring default scanning policy to make our configuration to take effect (Spring Boot entry class @SpringBootApplicationnotes contain some default scanning policy).

Start the project and observe the console:

As you can see, the controller and dao will no longer be included in the IOC container

Multi-scan strategy configuration

Before Java 8, we can use @ComponentScansto configure multiple @ComponentScanto achieve multi-scan rule configuration:

In Java 8, a new @Repeatableannotation is added , and the annotation modified with this annotation can be reused. If you look at the @ComponentScansource code, you will find that it has been marked by the annotation:

So in addition to using @ComponentScansto configure multiple scanning rules, we can also @ComponentScanspecify multiple different scanning rules through multiple uses .

Custom scan strategy

Custom scanning strategy requires us to implement an org.springframework.core.type.filter.TypeFilterinterface, create and MyTypeFilterimplement this interface:

public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return false;
    }
}

This interface contains matchmethods, the two input parameters MetadataReaderand MetadataReaderFactorymeanings are as follows:

  1. MetadataReader: Information about the class currently being scanned;

  2. MetadataReaderFactory: You can use it to get other types of information.

When the matchmethod returns true, the match is successful, and false means the match failed. Continue to improve this filtering rule:

public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
        // 获取当前正在扫描的类的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 获取当前正在扫描的类的路径等信息
        Resource resource = metadataReader.getResource();

        String className = classMetadata.getClassName();
        return StringUtils.hasText("er");
    }
}

It is specified above that when the scanned class name is included er, the matching is successful. Cooperative excludeFiltersuse means that when the scanned class name is included er, the class is not included in the IOC container.

We @ComponentScanuse this custom filtering strategy in:

@Configuration
@ComponentScan(value = "cc.mrbird.demo",
        excludeFilters = {
            @Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
        })
public class WebConfig {
}

Start the project and observe the output:

Because User, UserMapper, UserServiceand UserControllerthe class name of the class and the like are included er, they have not been incorporated into the IOC container.

 

Component scope @Scope

By default, each component in Spring's IOC container is singleton, that is, no matter how many times it is injected anywhere, these objects are the same. Let's look at the following example.

First @Componentremove the annotations in the User object , and then configure the User Bean in the configuration class:

@Configuration
public class WebConfig {
    @Bean
    public User user() {
        return new User("mrbird", 18);
    }
}

Then get this component from the IOC container multiple times to see if it is the same:

// 返回 IOC 容器,使用注解配置,传入配置类
ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
Object user1 = context.getBean("user");
Object user2 = context.getBean("user");
System.out.println(user1 == user2);

Start the project and observe the console output:

The results confirmed the above point.

In Spring, we can use @Scopeannotations to change the scope of components:

  1. singleton: Single instance (default), when the Spring IOC container starts, the method will be called to create the object and then included in the IOC container, and each subsequent acquisition will be directly obtained from the IOC container ( map.get());

  2. prototype: Multi-instance, the IOC container will not create an object when it is started, but will call the method to create the object every time it is acquired;

  3. request: One request corresponds to one instance;

  4. session: The same session corresponds to one instance.

Lazy loading @Lazy

Lazy loading is for the singleton mode. As mentioned earlier, the components in the IOC container are singleton by default. When the container starts, methods are called to create objects and then included in the IOC container.

Add a sentence at the place where User Bean is registered to observe:

@Configuration
public class WebConfig {
    @Bean
    public User user() {
        System.out.println("往IOC容器中注册user bean");
        return new User("mrbird", 18);
    }
}

test:

ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
System.out.println("容器创建完毕");

Start the project and observe the console output:

As you can see, before the IOC container is created, the component has been added to the container.

Change User Bean to lazy loading:

@Configuration
public class WebConfig {
    @Bean
    @Lazy
    public User user() {
        System.out.println("往IOC容器中注册user bean");
        return new User("mrbird", 18);
    }
}

Start the project again and observe the output:

As you can see, when the container is created, the User Bean component is not added to the container.

So the function of lazy loading is that in the singleton mode, when the IOC container is created, it will not immediately call the method to create the object and register it. Only when the component is used for the first time will it call the method to create the object and add it to the container. .

have a test:

ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
System.out.println("容器创建完毕");
Object user1 = context.getBean("user");
Object user2 = context.getBean("user");

Start the project and observe the output:

Conditional registration component

@Conditional

Using @Conditionalannotations, we can specify the conditions for component registration, that is, only when certain conditions are met can the component be included in the IOC container.

Before using this annotation, we need to create a class to implement the Conditioninterface:

public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

This interface contains one matchesmethod and two input parameters:

  1. ConditionContext: Contextual information;

  2. AnnotatedTypeMetadata: Annotation information.

Simply improve this implementation class:

public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String osName = context.getEnvironment().getProperty("os.name");
        return osName != null && osName.contains("Windows");
    }
}

Then add this condition to where User Bean is registered:

@Bean
@Conditional(MyCondition.class)
public User user() {
    return new User("mrbird", 18);
}

In the Windows environment, the User component will be successfully registered. If it is another operating system, this component will not be registered in the IOC container.

@Profile

@ProfileDifferent components can be registered according to different environment variables. Let's learn how to use it.

Create a new interface CalculateService:

public interface CalculateService {
    Integer sum(Integer... value);
}

Then add two implementations Java7CalculateServiceImpland Java8CalculateServiceImpl:

@Service
@Profile("java7")
public class Java7CalculateServiceImpl implements CalculateService {
    @Override
    public Integer sum(Integer... value) {
        System.out.println("Java 7环境下执行");
        int result = 0;
        for (int i = 0; i <= value.length; i++) {
            result += i;
        }
        return result;
    }
}
@Service
@Profile("java8")
public class Java8CalculateServiceImpl implements CalculateService {
    @Override
    public Integer sum(Integer... value) {
        System.out.println("Java 8环境下执行");
        return Arrays.stream(value).reduce(0, Integer::sum);
    }
}

Through @Profileannotations, we achieved: when environment variables are included java7, they Java7CalculateServiceImplwill be registered in the IOC container; when environment variables are included java8, they Java8CalculateServiceImplwill be registered in the IOC container.

have a test:

ConfigurableApplicationContext context1 = new SpringApplicationBuilder(DemoApplication.class)
                .web(WebApplicationType.NONE)
                .profiles("java8")
                .run(args);

CalculateService service = context1.getBean(CalculateService.class);
System.out.println("求合结果: " + service.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

Start the project, the console output is as follows:

If it is .profiles("java8")changed .profiles("java7"), the console output is as follows:

Import components

@Import

So far, we can use package scanning and @Beanto achieve component registration. In addition, we can also use it @Importto quickly add components to the IOC container.

Create a new class Hello:

public class Hello {
}

Then import this component in the configuration class:

@Configuration
@Import({Hello.class})
public class WebConfig {
	...
}

View the names of all components in the IOC container:

ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
String[] beanNames = context.getBeanDefinitionNames();
Arrays.stream(beanNames).forEach(System.out::println);

Start the project, the console output:

As you can see, @Importwe can quickly add components to the IOC container, and the Id defaults to the full class name.

ImportSelector

By @ImportWe have achieved imported components, if one-time import more components, we can use ImportSelectorto achieve.

Add three new categories Apple, Bananaand Watermelon, the code is omitted.

View ImportSelectorsource code:

public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
     String[] selectImports(AnnotationMetadata importingClassMetadata);
}

ImportSelectorIs an interface, including a selectImportsmethod, the method returns the full class name array of the class (that is, the full class name array of the component that needs to be imported into the IOC container), including a AnnotationMetadatatype input parameter, through this parameter we can get ImportSelectorall the classes used Annotation information.

We create a new ImportSelectorimplementation class MyImportSelector:

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                "cc.mrbird.demo.domain.Apple",
                "cc.mrbird.demo.domain.Banana",
                "cc.mrbird.demo.domain.Watermelon"
        };
    }
}

The above method returns an array of the full class names of the three newly added classes, and then we @Importuse MyImportSelectorthem in the annotations of the configuration classes to quickly import these three components into the IOC container:

@Configuration
@Import({MyImportSelector.class})
public class WebConfig {
    ...
}

Check if these three components are already in the container:

ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
String[] beanNames = context.getBeanDefinitionNames();
Arrays.stream(beanNames).forEach(System.out::println);

Start the project and observe the console:

The component has been successfully imported.

ImportBeanDefinitionRegister

In addition to the above two methods of importing components into the IOC container, we can also use ImportBeanDefinitionRegistrarto manually import components into the IOC container.

View its source code:

public interface ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

ImportBeanDefinitionRegistrarIs an interface, including a registerBeanDefinitionsmethod, the method includes two input parameters:

  1. AnnotationMetadata: You can get the annotation information of the class through it;

  2. BeanDefinitionRegistry: Bean definition register, contains some methods related to Bean:

public interface BeanDefinitionRegistry extends AliasRegistry {
    void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;

    void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

    BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

    boolean containsBeanDefinition(String var1);

    String[] getBeanDefinitionNames();

    int getBeanDefinitionCount();

    boolean isBeanNameInUse(String var1);
}

Here we need to use BeanDefinitionRegistrythe registerBeanDefinitionmethod to register the Bean in the IOC container. This method contains two input parameters, the first is the Bean name (Id) that needs to be registered, and the second parameter is the definition information of the Bean. It is an interface, and we can use its implementation class RootBeanDefinitionto complete:

In order to demonstrate ImportBeanDefinitionRegistrarthe use, we first add a new class, the name is Strawberry, the code is omitted.

Then add a new ImportBeanDefinitionRegistrarimplementation class MyImportBeanDefinitionRegistrar:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        final String beanName = "strawberry";
        boolean contain = registry.containsBeanDefinition(beanName);
        if (!contain) {
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Strawberry.class);
            registry.registerBeanDefinition(beanName, rootBeanDefinition);
        }
    }
}

In the above implementation class, we first pass BeanDefinitionRegistrythe containsBeanDefinitionmethod to determine whether the IOC container contains the strawberrycomponent named as , if not, register one manually through BeanDefinitionRegistrythe registerBeanDefinitionmethod.

After the definition MyImportBeanDefinitionRegistrar, we also @Importuse it in the configuration class :

@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
public class WebConfig {
    ...
}

Check if this component is already in the container:

ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
String[] beanNames = context.getBeanDefinitionNames();
Arrays.stream(beanNames).forEach(System.out::println);

 Start the project and observe the console:

The component has been successfully registered.

Use FactoryBean to register components

Spring also provides an FactoryBeaninterface, we can register components by implementing the interface, the interface contains two abstract methods and a default method:

In order to demonstrate FactoryBeanthe use, we add a new Cherryclass, the code is omitted.

Then create FactoryBeanthe implementation class CherryFactoryBean:

public class CherryFactoryBean implements FactoryBean<Cherry> {
    @Override
    public Cherry getObject() {
        return new Cherry();
    }

    @Override
    public Class<?> getObjectType() {
        return Cherry.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

getObjectReturns the component object that getObjectTypeneeds to be registered , returns the type of component that needs to be registered, and isSingletonindicates whether the component is a singleton. If there are multiple cases, its getObjectmethod will be called every time the component is retrieved from the container .

After the definition CherryFactoryBean, we register this class in the configuration class:

@Bean
public CherryFactoryBean cherryFactoryBean() {
    return new CherryFactoryBean();
}

The test is obtained from the container:

ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
Object cherry = context.getBean("cherryFactoryBean");
System.out.println(cherry.getClass());

Start the project and observe the console output:

It can be seen that although what we get is cherryFactoryBeanthe component with Id , what it gets is actually getObjectthe object returned in the method.

If we want to get cherryFactoryBeanitself, we can do this:

Object cherryFactoryBean = context.getBean("&cherryFactoryBean");
System.out.println(cherryFactoryBean.getClass());

Start the project and observe the console:

Why &can I get the corresponding factory class by adding a prefix? Check BeanFactorythe source code and you will find the reason:

When the imported component class is annotated with @configuration (configuration class), in addition to this configuration class will be added to the IOC container, the beans defined in the configuration class (marked by @bean) will also be added to the IOC container.

 

 

 

Guess you like

Origin blog.csdn.net/u014225733/article/details/100834591