Major changes related to Spring Framework 5.0 DI container

This is the third installment of the "Spring Framework 5.0 Major Changes" series, and the main changes (new features and improvements, etc.) related to DI containers. ) I would like to introduce.

series

Operation verification version

DI container related changes

In Spring Framework 5.0, the following changes have been made to DI containers.

Item number Changes
1 Ability to scan components from the classpath and register beans in the DI container when the application is executed(@ComponentScan)Alternative to(Purpose of shortening and stabilizing application startup time)As a result, a mechanism to create an index of component candidates to be registered as beans at compile time will be added.[To details:arrow_right:]
2 Injection by auto wiring is optional(option)Provided from your own or 3rd party library to indicate that@NullableWill be able to be specified.[To details:arrow_right:]

**Note:**Annotation name is"Nullable"It is judged by whether or not it is.
3 GenericApplicationContextWhenAnnotationConfigApplicationContextFunctional interface(Supplier)Bean registration method using(registerBean)WhenBean定義をカスタマイズするためのインタフェース(BeanDefinitionCustomizer)Will be added.[To details:arrow_right:]
4 When applying AOP to a class that implements an interface using "CGLIB Proxy"(proxyTargetClass=trueWhen specified)Annotation specified in the interface method(@Transactional, @Cacheable, @SyncSuch)Will be read.[To details:arrow_right:]
5 Generation management of xsd file specified when defining a bean in XML(Providing xsd files for past versions)Will be abolished and only xsd files for that version will be stored in the JAR file.[To details:arrow_right:]

**Note:**Specifying a versioned xsd file within an XML file is still supported, but the same xsd file is always used when parsing the XML file.

Component index scan is supported: thumbsup:

[SPR-11890]: A function to scan the class under the package specified at the time of application execution and register the bean in the DI container (@ComponentScan" ), But by default, the target class is searched from the classpath at runtime. With this mechanism, the time required to scan a class depends on the package configuration and the number of classes. From Spring Framework 5.0, a mechanism to resolve scan candidate classes at compile time will be added as an alternative method of classpath scanning (for the purpose of shortening or stabilizing the application startup time).

Note:

JIRA's comments don't seem to make it dramatically faster, and it doesn't seem to make it faster, so it's best to actually look at the effects and decide whether or not to use index scans. I think. In addition, in the test case created for operation verification (the total number of scan candidate classes and classes is about 10 classes), the classpath scan was slightly faster. (It's a difference of about several tens of msec: sweat_smile :)

To briefly explain the mechanism ... Using the "JSR 269: Pluggable Annotation Processing API" added in JDK 1.6, "Index file ( / META-) for acquiring scan candidate classes at compile time. INF / spring.components) ”is created and the file is read at runtime. (If you try to make a picture in vain ... it looks like the following)

spring50-indexer-overview.png

The classes to be output from the index file at compile time are "composite annotations that specify @Indexed and @Indexed as meta annotations (such as @Component) "and annotations that start with javax. (such as javax.". @Named and @ManagedBean) "" package-info class ". At the time of execution, the following Map (index) is generated based on the index file, and the class list of scan candidates is acquired without actually scanning the package to be scanned for components.

spring50-indexer-indexmap.png

As with the classpath scan, "classes outside the package to be scanned", "classes that match the exclusion filter", and "classes that do not match the inclusion filter" are not registered in the DI container.

Enabling index scanning is easy, just add the spring-context-indexer module to the dependent artifacts as follows:

pom.xml


<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-indexer</artifactId>
  <version>5.0.0.RC1</version>
  <optional>true</optional> <!--Avoid packaging in war etc. as it is only needed when compiling-->
</dependency>

Note:

If there is at least one valid index file (/META-INF/spring.components) on the classpath, index scan is used by default. For example, if your project does not use spring-context-indexer, but its dependent libraries (such as your company's common library) contain index files, your project's components will not be scanned. To avoid this event, you have two choices: "Use spring-context-indexer in your own project "or" Do not scan the index at runtime ". To "prevent index scanning at runtime", go to "Spring Properties File ( spring.properties directly under the classpath) "or" Java System Properties ( -D Option)" to " spring Please specify .index.ignore = true ".

@Nullable can be specified for any injection point: thumbsup:

[SPR-15028]: Homebrew or 3rd party library ([SPR-15028]: Autowiring injection is optional (optional) You will be able to specify the @Nullable provided by JSR-305](https://jcp.org/en/jsr/detail?id=305).

Note:

Internally, it judges whether the annotation name is " Nullable " or not, so it also recognizes your own @ Nullable.

For example ...

〜4.3


@Component
public class Foo {

	private final Bar bar;
	private final Baz baz;

	public Foo(Bar bar, @Autowired(required = false) Baz baz) {
		this.bar = bar;
		this.baz = baz;
	}

}

Can also be rewritten in a style that does not use Spring Framework annotations (example: JSR-330 annotation + JSR-305 @ Nullable) as shown below.

pom.xml


<dependency>
  <groupId>javax.inject</groupId>
  <artifactId>javax.inject</artifactId>
  <version>1</version>
</dependency>
<dependency>
  <groupId>com.google.code.findbugs</groupId>
  <artifactId>jsr305</artifactId>
  <version>3.0.2</version>
</dependency>

@Use of Nullable(5.0〜)


import javax.annotation.Nullable;
import javax.inject.Named;
//...
@Named
public class Foo {

	private final Bar bar;
	private final Baz baz;

	public Foo(Bar bar, @Nullable Baz baz) {
		this.bar = bar;
		this.baz = baz;
	}

}

By the way ... JSR-330 annotations (@Named and @Inject) can also be used in Spring Framework 4.3. You can also use the java.util.Optional added in JDK 8 as a way to indicate that it is an arbitrary injection point.

Use of Optional


import java.util.Optional;
// ...
@Named
public class Foo {

	private final Bar bar;
	private final Optional<Baz> baz;

	public Foo(Bar bar, Optional<Baz> baz) {
		this.bar = bar;
		this.baz = baz;
	}

}

Beans can be registered using the functional interface (Supplier): thumbsup:

[SPR-14832]: Bean registration method (Supplier) using functional interface (Supplier) for GenericApplicationContext and ʻAnnotationConfigApplicationContext An interface (BeanDefinitionCustomizer) for customizing the registerBean) and bean definition is added.

For example, if you do the following, the object returned from the Supplier specified in the lambda expression will be managed in the DI container as a singleton bean.

Bean generation and registration using Supplier


try (AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext()) {
	applicationContext.registerBean(Bar.class);
	applicationContext.registerBean(Foo.class, () -> new Foo(applicationContext.getBean(Bar.class))); //★★★ Bean registration using Supplier
	applicationContext.refresh();

	Foo foo = applicationContext.getBean(Foo.class);

	// ...
}

Furthermore ... By using the BeanDefinitionCustomizer interface, you can specify the meta information (scope, etc.) of the bean managed by the DI container. In the sample below, the scope of the bean is changed to prototype.

java:org.springframework.beans.factory.config.BeanDefinitionCustomizer


@FunctionalInterface
public interface BeanDefinitionCustomizer {
	void customize(BeanDefinition bd);
}

Example of bean definition customization using BeanDefinitionCustomizer


try (AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext()) {
	applicationContext.registerBean(Bar.class);
	applicationContext.registerBean(Foo.class, () -> new Foo(applicationContext.getBean(Bar.class)),
			bd -> bd.setScope(BeanDefinition.SCOPE_PROTOTYPE)); //★★★ Customize Bean Definitions with BeanDefinitionCustomizer
	applicationContext.refresh();

	Foo foo = applicationContext.getBean(Foo.class);

	Assertions.assertFalse(foo == applicationContext.getBean(Foo.class)); //Since the scope is a prototype, different instances are returned the first time and the second time
}

Note:

By the way ... If you don't use any annotations provided by Spring (do you want to do annotation-driven development?), You can also use GenericApplicationContext.

The annotation specified for the interface is read when using "CGLIB Proxy": thumbsup:

[SPR-14322 etc.]: When applying AOP to a class that implements an interface using "CGLIB Proxy" ( When proxyTargetClass = true is specified), the annotations specified in the interface method (@Transactional, @Cacheable, @Sync, etc.) will be read.

For example, when using Spring's cache function as shown below, annotate the interface with cache control.

interface


@CacheConfig(cacheNames = "accounts")
public interface AccountService {
	@Cacheable
	Account getAccount(int id);
}

Implementation class


public class AccountServiceImpl implements AccountService {
	@Override
	public Account getAccount(int id) {
		return new Account(id);
	}
}

In the configuration class, set to use CGLIB Proxy instead of JDK Proxy when applying Spring cache function (when creating Proxy object).

configuration


@EnableCaching(proxyTargetClass = true) //★★★ Set to use CGLIB Proxy
@Configuration
public static class CacheConfiguration {
	@Bean
	CacheManager cacheManager() {
		return new ConcurrentMapCacheManager("accounts");
	}
	@Bean
	AccountService accountService() {
		return new AccountServiceImpl();
	}
}

In this state, if you call the AccountService method obtained from the application context twice with the same argument, the cached object will be returned the second time.

python


try (AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(
		CacheConfiguration.class)) {

	AccountService service = applicationContext.getBean(AccountService.class);

	Account account = service.getAccount(1);

	Assertions.assertTrue(account == service.getAccount(1)); //Spring's cache function is applied, and the cached object is returned the second time.
}

Abolition of generation management of xsd files

[SPR-13499]: Generation management of xsd files specified when defining beans in XML (providing xsd files for past versions) It will be deprecated and only xsd files for that version will be stored in the JAR file. Specifying a versioned xsd file within an XML file is still supported, but the same xsd file is always used when parsing the XML file.

On Spring Framework 4.3, it was possible to use the attributes supported in the past version as follows.

Bean definition XML file


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"> <!-- ★★★ 3.Specify XSD file for 2-->

  <bean class="com.example.di.Foo">
    <property name="bar">
      <ref local="bar"/> <!-- ★★★ 3.x has local attribute-->
    </property>
  </bean>

  <bean id="bar" class="com.example.di.Bar"/>

</beans>

If you use this file with Spring Framework 5.0 ... It will be a schema violation when parsing the XML file, and the following error will occur.

org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 9 in XML document from class path resource [com/example/di/applicationContext.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 9; columnNumber: 25; cvc-complex-type.3.2.2:element'ref'Attribute to'local'Cannot be included.

	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:399)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)

In order to use this file on Spring Framework 5.0, it is necessary to take measures such as "change to bean attribute instead of local attribute" or "use ref attribute of <property> element ". Become. In addition, " local attribute of <ref> element "is an example of the attribute that can not be used, and some other things can not be used.

Example of changing to bean attribute


<bean class="com.example.di.Foo">
  <property name="bar">
    <ref bean="bar"/>
  </property>
</bean>

<property>Example of using the ref attribute of an element


<bean class="com.example.di.Foo">
  <property name="bar" ref="bar"/>
</bean>

Also, there is no point in specifying the version when specifying the XSD file (rather confusing), so it is recommended to take this opportunity to change to specify the XSD file that does not include the version.

Example of specifying an unversioned XSD file


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--★★★ Change to unversioned file-->

  <!-- ... -->

</beans>

Summary

This time, we introduced the main changes related to DI containers. Perhaps "index scan support" is the main change, but it doesn't seem to start up dramatically faster, so look forward to future improvements! !! Is it something like: wink: Next time, I will introduce "Major changes related to WebMVC".

Recommended Posts

Major changes related to Spring Framework 5.0 DI container
Major changes related to Spring Framework 5.0 Test
Major changes related to Spring Framework 5.0 Web MVC
Spring Framework study notes [Part 1] DI container
Major changes in Spring Framework 5.0 core functionality
Spring Framework Summary-About DI
Introduction to Spring Boot ① ~ DI ~
Major changes in Spring Boot 1.5
About Spring DI related annotations
Changes when migrating from Spring Boot 1.5 to Spring Boot 2.0
Changes when migrating from Spring Boot 2.0 to Spring Boot 2.2
I tried to link JavaFX and Spring Framework.
[Java] Spring DI ③
Try using DI container with Laravel and Spring Boot
How to set Dependency Injection (DI) for Spring Boot
How to use Struts2 * Spring Framework (Spring plugin) June 2017 Version