iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
😎

N Things to Watch Out for When Reading 'Introduction to Spring' and 'Comprehensive Spring' in the Reiwa Era

に公開

About This Article

As I mention every time I write or speak, even now as we approach 2020, there are still only two reliable Spring-related books:

Both books (hereafter "the books") are excellent, but they were both released in 2016, and the supported Spring version is 4.2, which is now old.

The latest version as of the end of 2019 is Spring 5.2. In this article, I will introduce several points to be particularly careful about when reading these books in the current Reiwa era.

For all differences from 4.x to 5.x, please check the GitHub Wiki.

Use JDK 8 or Higher

Starting from Spring 5.0, the JDK baseline has been moved to 8 (Spring 4 was based on JDK 6). I don't think anyone trying to use Spring now would attempt to use JDK 6 or 7, but...

In Spring 5.2, support extends up to JDK 14. For details on the version compatibility between JDK and Spring, please refer to the GitHub Wiki.

Use Constructor Injection + Omission of @Autowired Instead of Field Injection

Since before Spring 4.2, there have been three ways to perform DI.

Example of field injection
@Component
public class Hoge {
    @Autowired
    Fuga fuga;
}
Example of setter injection
@Component
public class Hoge {
    private Fuga fuga;

    @Autowired
    public void setFuga(Fuga fuga) {
        this.fuga = fuga;
    }
}
Example of constructor injection
@Component
public class Hoge {
    private final Fuga fuga;

    @Autowired
    public Hoge(Fuga fuga) {
        this.fuga = fuga;
    }
}

Field injection is the method most commonly used in the books. This is likely because it requires the least amount of code (and perhaps due to space constraints on the page).

From Spring 4.3 onwards, if a class has only one constructor, the @Autowired annotation can be omitted.

Example of constructor injection (4.3 or later)
@Component
public class Hoge {
    private final Fuga fuga;

    // Since there is only one constructor, @Autowired can be omitted!
    public Hoge(Fuga fuga) {
        this.fuga = fuga;
    }
}

Since it allows the class to be immutable, you should use constructor injection whenever possible.

Use @GetMapping or Similar Instead of @RequestMapping

In Spring MVC, you use @RequestMapping to write controller classes and methods.

4.2 or earlier
@Controller
@RequestMapping("/hoge")
public class HogeController {

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index() {
        return "index";
    }
}

From Spring 4.3, annotations for each HTTP request method, such as @GetMapping and @PostMapping, were introduced. It's great because you can write them very concisely!

4.3 or later
@Controller
@RequestMapping("/hoge") // It's fine to leave this as @RequestMapping
public class HogeController {

    @GetMapping("/index") // Use @XxxMapping!
    public String index() {
        return "index";
    }
}

Use Thymeleaf 3 and Write in HTML Format (Not XHTML)

The books use Thymeleaf 2. To write screens in Thymeleaf 2, you need to use the XHTML format.

By adding a library, it is possible to write in HTML format even with version 2.

2 or earlier
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Screen</title>
</head>
<body>
<form action="index.html" th:action="@{findByFirstName}">
    First Name Keyword: <input type="text" name="firstName" />
    <input type="submit" value="Search" />
</form>
...

In Thymeleaf 3, you can write in HTML format by default.

3 or later
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Screen</title>
</head>
<body>
<form action="index.html" th:action="@{findByFirstName}">
    First Name Keyword: <input type="text" name="firstName">
    <input type="submit" value="Search">
</form>
...

It's great that you no longer have to worry about runtime exceptions from forgetting a /!

Use the WebMvcConfigurer Interface Instead of the WebMvcConfigurerAdapter Class

In Spring 4.3 and earlier, when creating Java Config for Spring MVC, the WebMvcConfigurerAdapter class is often inherited.

4.3 or earlier
@Configuration
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        ...
    }
    ...
}

From Spring 5.0, this class became deprecated, and it is now recommended to use the WebMvcConfigurer interface that this class implements.

5.0 or later
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        ...
    }
    ...
}

The WebMvcConfigurerAdapter class was a class that implemented the WebMvcConfigurer interface and overrode all methods with empty implementations. With the move to a Java 8 baseline, all methods in the WebMvcConfigurer interface became default methods with empty implementations. Since its role was fulfilled, the WebMvcConfigurerAdapter class became deprecated.

Source code for WebMvcConfigurer

Use Bean Validation's @NotBlank Instead of Hibernate Validator's @NotBlank

Starting from Spring 5, with support for Java EE 8, the Bean Validation version has been upgraded from 1.x to 2.0 (Hibernate Validator 6.0 or later).

While it is possible to use Bean Validation 1.x with Spring 5, I generally recommend using the latest version.

In Bean Validation 2.0, new constraint annotations were added. Some of these were originally Hibernate Validator-specific annotations that have now been incorporated into the Bean Validation standard. Specifically, these are the following annotations (all in the javax.validation.constraints package):

  • @NotEmpty
  • @NotBlank
  • @Email

Accordingly, Hibernate Validator's proprietary @NotBlank, @NotEmpty, and @Email (all in the org.hibernate.validator.constraints package) have been deprecated.

Note the Lack of Compatibility in Spring Data's CrudRepository

From Spring Data 2.x onwards, the names and return values of methods defined in CrudRepository have changed.

The changes include support for java.util.Optional, method name updates, etc.

CrudRepository for 1.x or earlier
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

	<S extends T> S save(S entity);

	<S extends T> Iterable<S> save(Iterable<S> entities);

	T findOne(ID id);

	boolean exists(ID id);

	Iterable<T> findAll();

	Iterable<T> findAll(Iterable<ID> ids);

	long count();

	void delete(ID id);

	void delete(T entity);

	void delete(Iterable<? extends T> entities);

	void deleteAll();
}
CrudRepository for 2.x or later
public interface CrudRepository<T, ID> extends Repository<T, ID> {

	<S extends T> S save(S entity);

	<S extends T> Iterable<S> saveAll(Iterable<S> entities);

	Optional<T> findById(ID id);

	boolean existsById(ID id);

	Iterable<T> findAll();

	Iterable<T> findAllById(Iterable<ID> ids);

	long count();

	void deleteById(ID id);

	void delete(T entity);

	void deleteAll(Iterable<? extends T> entities);

	void deleteAll();
}

Always Explicitly Configure Spring Security's PasswordEncoder

In Spring Security 4 and earlier, if you did not explicitly specify a PasswordEncoder, password hashing was not performed.

In Spring Security 5 and later, if you do not explicitly specify a PasswordEncoder, DelegatingPasswordEncoder is used. This reads the prefix of the password stored in the database or elsewhere and delegates the processing to the appropriate PasswordEncoder.

<img width="866" alt="Screenshot 2019-12-31 11.03.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58650/d1e1471a-46c5-11ca-22e3-93370139848e.png">

I don't think anyone is not specifying a PasswordEncoder in a production environment (at least I want to believe so). However, it might have been omitted for simplicity in study-related code. In Spring Security 5.x and later, make sure to always specify it. Defining the PasswordEncoder as a Bean is sufficient. Of course, ensure that passwords stored in the database are also hashed using the same algorithm's PasswordEncoder.

Specifying PasswordEncoder
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    ...
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Be Aware That the Content of Spring Boot Has Changed Significantly

Regarding the transition from Spring Boot 1.x to 2.x, there are too many changes to list them all (lol).

Just thinking off the top of my head:

  • Update of various libraries
    • Spring 4.x -> 5.x
    • Spring Data 1.x -> 2.x
    • Spring Security 4.x -> 5.x
    • Thymeleaf 2 -> 3
    • Hibernate -> 5.4
    • Jackson -> 2.10
    • Hibernate Validator 5.x -> 6.1
    • Flyway 4 -> 6
  • JDK 8 support
    • thymeleaf-extras-java8time already added
    • jackson-datatype-jdk8, etc., already added
  • Simplification of security
    • Most security-related properties were removed
  • Improvements to Actuator
    • Fundamental changes to internal architecture
    • Changes to authentication and authorization
    • Addition of Micrometer
    • Property changes
  • Renaming of properties
    • Names of various properties have been significantly changed

...and so on. There might be many others.

Update: 2020-02-12

I am adding some materials presented at JSUG study sessions. Both are valuable resources, so please take a look!

Study Session Slides

List of blog posts by @b1a9idps

Update: 2020-10-16

In Spring Boot 2.3 and later, Hibernate Validator is no longer included in spring-boot-starter-web (Release Note).

If you want to use Hibernate Validator, you need to add spring-boot-starter-validation.

(Update: 2022-06-16) Spring Security's WebSecurityConfigurerAdapter Has Been Deprecated

The way security settings are written has changed significantly. Please refer to the article below 👇 for details.

https://qiita.com/suke_masa/items/908805dd45df08ba28d8

Conclusion

I will continue to add more as I think of them.

Discussion