[JAVA] Major changes in Spring Framework 5.0 core functionality

This is the second installment of the "Spring Framework 5.0 Major Changes" series, and the main changes in core functions (new functions, improvements, etc.) I would like to introduce.

series

Operation verification version

Changes in core functionality

In Spring Framework 5.0, the following changes have been made to general-purpose functions (= core functions) that do not depend on the type of application.

Item number Changes
1 You will be able to efficiently access method parameters using the mechanisms supported by JDK 8.[To details:arrow_right:]

**Note:**Since it is an internal implementation, the external specifications do not change.
2 Some interfaces will now implement the default methods supported by the JDK 8.[To details:arrow_right:]

**Note:**Whencreatinganimplementationclass(extensionclass) for those interfaces, it is only necessary to implement the necessary methods, so there are measures such as "creating an Adapter class that provides an empty implementation" and "unnecessarily performing an empty implementation". It will be unnecessary.
3 Supported in JDK 7StandardCharsetsWill be used.[To details:arrow_right:]

**Note:**Since it is an internal implementation, the external specifications do not change.
4 Will be deprecated in JDK 9Class#newInstance()Instead ofConstructor#newInstance()Will be called to instantiate.[To details:arrow_right:]

**Note:**Since it is an internal implementation, the external specifications do not change.
5 spring-jclModules have been added and Log4j 2 via the Commons Logging API.x, SLF4J, JUL(java.util.logging)You will be able to output the log via.[To details:arrow_right:]

Note: Spring Framework 4.The library for log bridge, which was required up to 3, is no longer required.
6 Interface for abstracting resources(Resource)ToisFile()メソッドが追加され、リソースがファイルシステム上To存在するか判定できるようToなります。[To details:arrow_right:]
7 Interface for abstracting resources(Resource)ToreadableChannel()Method added,ReadableByteChannel(New I/O)経由でリソースのデータを読み込むことができるようToなります。[To details:arrow_right:]

Method parameters can be accessed via API added in JDK 8: thumbsup:

[SPR-14055]: Starting with JDK 8, the java.lang.reflect.Method and java.lang.reflect.Constructor classes are It now inherits the java.lang.reflect.Executable class added from JDK 8, and Spring Framework 5.0 now uses the methods of the ʻExecutable` class to access method and constructor parameter information. became. Since the class modified this time is basically a class used in the internal processing of the framework, I think that it is not often used directly by application developers, but it is used when creating AP infrastructure parts such as framework extensions. There may be a possibility.

For example ...

package com.example;

import org.springframework.beans.factory.annotation.Value;

public class Foo {
	public Foo(String text, int number) {
		// ...
	}
	public String bar(@Value("${text:dummy}") String text) {
		return text;
	}
}

Let's take a look at the code that accesses the constructor and method arguments of this class.

In Spring Framework 4.3, use the forMethodOrConstructor method of the MethodParameter class to get the parameter information. (Deprecated API in Spring Framework 5.0)

〜4.3


package com.example;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

class CoreTest {

	@Test
	void constructorParameter() throws Exception {
		Constructor<?> constructor = Foo.class.getConstructor(String.class, int.class);
		MethodParameter parameter0 = MethodParameter.forMethodOrConstructor(constructor, 0);
		MethodParameter parameter1 = MethodParameter.forMethodOrConstructor(constructor, 1);

		Assertions.assertEquals(String.class, parameter0.getParameterType());
		Assertions.assertEquals(int.class, parameter1.getParameterType());
	}

	@Test
	void methodParameter() throws Exception {
		Method method = Foo.class.getMethod("bar", String.class);
		MethodParameter parameter0 = MethodParameter.forMethodOrConstructor(method, 0);

		Assertions.assertEquals(String.class, parameter0.getParameterType());
		Assertions.assertEquals("${test:dummy}", parameter0.getParameterAnnotation(Value.class).value());
	}

}

In Spring Framework 5.0, use the forExecutable or forParameter method of the MethodParameter class to get the parameter information.

5.0〜


package com.example;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

class CoreTest {

	@Test
	void constructorParameter() throws Exception {
		Constructor<?> constructor = Foo.class.getConstructor(String.class, int.class);
		MethodParameter parameter0 = MethodParameter.forExecutable(constructor, 0);
		MethodParameter parameter1 = MethodParameter.forExecutable(constructor, 1);

		Assertions.assertEquals(String.class, parameter0.getParameterType());
		Assertions.assertEquals(int.class, parameter1.getParameterType());
	}

	@Test
	void methodParameter() throws Exception {
		Method method = Foo.class.getMethod("bar", String.class);
		MethodParameter parameter0 = MethodParameter.forParameter(method.getParameters()[0]);

		Assertions.assertEquals(String.class, parameter0.getParameterType());
		Assertions.assertEquals("${text:dummy}", parameter0.getParameterAnnotation(Value.class).value());
	}

}

Note:

By the way ... If you want to get the actual parameter name, you need to set DefaultParameterNameDiscoverer to MethodParameter. (If you want to use this mechanism, you need to specify " -parameters "or" -g` "as a compile option.)

MethodParameter parameter0 = MethodParameter.forParameter(method.getParameters()[0]);
parameter0.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
// ...
Assertions.assertEquals("text", parameter0.getParameterName());

#Default method implemented in interface:thumbsup:

[SPR-14432]:SomeoftheinterfacesprovidedbytheSpringFrameworkwillnowimplementthedefaultmethodssupportedbytheJDK8.Duetothischange,whencreatinganimplementationclass(extensionclass) for those interfaces, it is only necessary to implement the necessary methods, so "Create an Adapter class that provides an empty implementation" or "Uselessly perform an empty implementation". There is no need to take measures such as ".

For example, spring-Provided by the beans moduleBeanPostProcessorOn the interfacepostProcessBeforeInitialization(Method that is called back before performing the initial processing of the bean)WhenpostProcessAfterInitialization(A method that is called back after performing the initial processing of Bnea)Whenいう2つのメソッドがありますが、そちらか一方だけ実装したいWhenいうケースもあります。

Spring Framework 4.In 3, I had to implement two methods without fail:

〜4.3


@Configuration
public class AppConfig {
	@Bean
	BeanPostProcessor myBeanPostProcessor() {
		return new BeanPostProcessor() {
			@Override
			public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 // Example) Implement the code that changes the bean state before the initialization process is performed.
				return bean;
			}

			@Override
			public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 return bean; // Template implementation that returns bean as it is is required ...
			}
		};
	}
}

Spring Framework 5.At 0, you only need to implement the required methods as shown below.

5.0〜


@Configuration
public class AppConfig {
	@Bean
	BeanPostProcessor myBeanPostProcessor() {
		return new BeanPostProcessor() {
			@Override
			public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 // Example) Implement the code that changes the bean state before the initialization process is performed.
				return bean;
			}
		};
	}
}

#Standard Charsets are used for processing within the framework:thumbsup:

[SPR-14492]:Intheinternalprocessingoftheframework,"UTF-Standardsuchas8"CharsetWhenspecifying,"Charset#forName(String)Supported by JDK 7 instead ofStandardCharsetsWill be used. This has no effect on framework users,StandardCharsetsTo "US-ASCII」「ISO-8859-1」「UTF-8」「UTF-16BE」「UTF-16LE」「UTF-There are constants for "16", so let's all actively use these constants!

Constructor#newInstance()Is generated using:thumbsup:

[SPR-14486]:IntheinternalprocessingoftheframeworkDeprecatedinJDK9WillbeClass#newInstance()InsteadofConstructor#newInstance()Willbecalledtoinstantiate.Apparently...Class#newInstance()Isusedtohandlecheckedexceptionswhenacheckedexceptionwithadeclarationinthethrowsclauseoftheconstructoroccurs.(Addcatchorthrowsclause)Is not enforced, so it seems that the exception that occurred is thrown to the top unintentionally. This is also a story that does not affect the framework user at all, but it seems that there are cases where it is implemented to create an instance using reflection independently, so let's see what is the problem. ..

It's not really possible, but it's forced by the default constructorIOExceptionPrepare a class that generates.

public class Foo {
	public Foo () throws  IOException {
		throw new IOException("error.");
	}
	// ...
}

When creating an instance of the class above,new Foor(), The compiler willIOExceptionYou will be asked to handle.

new


@Test
void newInstance() {
	try {
		new Foo();
		Assertions.fail("does not occurred a IOException.");
 } catch (IOException e) {// ★★★ The compiler forces the handling of IOException! !!
		// NOP
	}
}

nextClass#newInstanceWhen you create an instance usingInstantiationExceptionWhenIllegalAccessExceptionのハンドリングを行うように求めてきます。 この状態でテストケースを実行するWhen・・・

Class.newInstance


@Test
void classNewInstance() {
	try {
		Foo.class.newInstance();
		Assertions.fail("does not occurred a IOException.");
	} catch (InstantiationException | IllegalAccessException e) {
		// NOP
	}
}
java.io.IOException: error.

	at com.example.CoreTest$Foo.<init>(CoreTest.java:97)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.lang.Class.newInstance(Class.java:442)
	at com.example.CoreTest.classNewInstance(CoreTest.java:75)

And the test fails. In other words ...InstantiationExceptionWhenIllegalAccessExceptionOccurs in the constructor even after handlingIOExceptionIs not handled and an exception is thrown unintentionally to the caller.

ThenClass#newInstancenotConstructor#newInstance()Let's see what happens when you use. If you run the test below,InvocationTargetExceptionOccurs,InvocationTargetExceptionException that occurred in the constructor inside(IOException)Is wrapped.

Constructor.newInstance


@Test
void constructorNewInstance() {
	try {
		Foo.class.getDeclaredConstructor().newInstance();
		Assertions.fail("does not occurred a IOException.");
 	} catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) {
		Assertions.fail("does not occurred a IOException.");
 } catch (InvocationTargetException e) {// ★★★ The compiler forces the handling of the exception that wraps the exception (IOException) that occurred in the constructor: sweat_smile! !!
		Assertions.assertEquals(IOException.class, e.getTargetException().getClass());
	}
}

spring-jcl module is added:thumbsup:

[SPR-14512]:spring-Ajclmodulehasbeenadded,andLog4j2viatheCommonsLoggingAPIwithoutusing"OriginalCommonsLogging"or"BridgelibrarythatimplementstheCommonsLoggingAPI"..x,SLF4J,JUL(java.util.logging)You will be able to output the log via. Spring Framework uses the API of Commons Logging to output logs, and the library for actual log output is in the style selected by the developer. A long time ago, "Commons Logging"+Log library(Log4j etc.)Was common, but recently "SLF4J"+Log ligatory that implements SLF4J API(For example Logback)Is becoming more mainstream (probably):sweat_smile:)。 For example ... "SLF4J+In the case where the application log is output by "Logback", the log output by Spring Framework is a bridge library that implements the API of Commons Logging.(jcl-over-slf4j etc.) + SLF4J +It should be configured as "Logback".

Let's see how to resolve the dependent libraries for the above configuration.

Spring Framework 4.In 3, spring-Since the core depends on "Commons Logging of the head family", spring when using the bridge library-I had to exclude "Original Commons Logging" from the core's dependent libraries.

xml:pom.xml(〜4.3)


<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.3.8.RELEASE</version>
      <exclusions>
        <exclusion>
          <groupId>commons-logging</groupId>
 <artifactId> commons-logging </ artifactId> <!-★★★ Exclude Commons Logging of the head family->
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>
</dependencyManagement>
<!-- ... -->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
 <artifactId> jcl-over-slf4j </ artifactId> <!-★★★ "Bridge library implementing Commons Logging API" is required->
  <version>1.7.25</version>
</dependency>

Spring Framework 5.From 0, spring the part of "Bridge library that implements API of Commons Logging"-Because jcl is responsible, jcl-over-Using the Commons Logging API, "Log4j 2" without adding a bridge library such as slf4j.x」「SLF4J」「JUL(java.util.logging)You can output the log via. Spring Framework 5.From 0, spring-core is spring instead of "commons logging of the head family"-spring because it depends on jcl-All you have to do is add the log library supported by jcl to the dependent libraries.

xml:pom.xml(5.0〜)


<!-- ... -->
<dependency>
  <groupId>ch.qos.logback</groupId>
 <artifactId> logback-classic </ artifactId> <!-★★★ You only need to add Logback that implements the SLF4J API->
  <version>1.2.3</version>
</dependency>

Note:

If the libraries used to build the application depend on "the original Commons Logging" or "the bridge library that implements the Commons Logging API", you need to exclude those libraries.

When I try to execute the code that generates the DI container of Spring ...

@Test
void applicationContext() {
	try (ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class)) {
		// ...
	}
}

The following log was output properly:v:

01:55:50.072 [main] DEBUG org.springframework.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
01:55:50.079 [main] DEBUG org.springframework.core.env.StandardEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence
01:55:50.080 [main] DEBUG org.springframework.core.env.StandardEnvironment - Initialized StandardEnvironment with PropertySources [systemProperties,systemEnvironment]
01:55:50.129 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning
01:55:50.179 [main] INFO org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@46daef40: startup date [Fri May 12 01:55:50 JST 2017]; root of context hierarchy
 ... (Omitted)
01:55:50.786 [main] INFO org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@46daef40: startup date [Fri May 12 01:55:50 JST 2017]; root of context hierarchy
01:55:50.786 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'lifecycleProcessor'
01:55:50.787 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@482cd91f: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,coreTest.AppConfig,myBeanPostProcessor]; root of factory hierarchy

ResourceCan be determined if is on the file system:thumbsup:

[SPR-14484]:Interfaceforabstractingresources(Resource)ToisFile()メソッドが追加され、リソースがファイルシステム上To存在するか判定できるようToなります。

@Test
void resourceIsFile() throws IOException {
	Resource fileResource = new FileSystemResource("pom.xml");
	Resource webResource = new UrlResource("http://google.com");

	Assertions.assertTrue(fileResource.isFile());
	Assertions.assertFalse(webResource.isFile());
}

ResourceDataReadableByteChannelCan be obtained via:thumbsup:

[SPR-14698]:Interfaceforabstractingresources(Resource)ToreadableChannel()Methodadded,ReadableByteChannel(NewI/O)経由でリソースのデータを読み込むことができるようToなります。ちなみTo・・・RC1WritableResourceTowritableChannelMethod added,WritableByteChannel経由でデータを書き込めるようToなっていたので、あわせて実装サンプルを紹介しておきます。

@Test
void resourceUsingByteBuffer() throws IOException {
	ByteBuffer buffer = ByteBuffer.allocate(2048);
	Resource srcResource = new FileSystemResource("pom.xml");
	WritableResource destResource = new FileSystemResource("pom.xml.bk");
	try (ReadableByteChannel readableChannel = srcResource.readableChannel();
			WritableByteChannel writableChannel = destResource.writableChannel()) {
		while (true) {
			buffer.clear();
			if (readableChannel.read(buffer) <= 0) {
				break;
			}
			buffer.flip();
			writableChannel.write(buffer);
		}
	}

	Assertions.assertEquals(srcResource.contentLength(), destResource.contentLength());
}

#Summary

This time, we have introduced the main changes in core functions. There was also an introduction of class changes that are not used directly when developing applications that use the framework, but improvements have been made to improve the "usability" and "efficiency" of the framework. It is an impression. next time,"Major changes related to DI containerWill be introduced.

Recommended Posts

Major changes in Spring Framework 5.0 core functionality
Major changes in Spring Boot 1.5
Spring Framework 5.0 Summary of major changes
Major changes related to Spring Framework 5.0 Test
Major changes related to Spring Framework 5.0 Web MVC
Major changes related to Spring Framework 5.0 DI container
Core Location changes in iOS 14
Changes in Mockito 2
Changes in mybatis-spring-boot-starter 2.0
Changes in mybatis-spring-boot-starter 2.1
Changes in mybatis-spring-boot-starter 1.3
Changes in Java 11
RESTful API multi-module sample in IntelliJ + Jersey + Spring Framework
Inject Logger in Spring
[Spring Framework] Configuration split
Spring Framework multilingual support
1. Start Spring framework from 1
Spring Framework Summary-About DI
Use Interceptor in Spring
Changes in JUnit5M4-> M5
Get cookies in Spring
Create Pivotal tc server Developer Edition in Spring Framework (STS)
Minimum configuration sample of RESTful API in Jersey + Spring Framework
Changes in mybatis-spring-boot-starter 2.1
Changes in mybatis-spring-boot-starter 1.3
Changes in mybatis-spring-boot-starter 1.2
Changes in Mockito 2
Changes in Java 11
Changes in JUnit5M4-> M5
Core Location changes in iOS 14
Major changes in Spring Boot 1.5
Java version notation that changes in Java 10
Major changes in Spring Framework 5.0 core functionality