Spring: Java Configuration
Spring is most popular framework for building web applications in Java ecosystem. Spring Framework can be configured either in XML, or with Java code. This page shows how to configure various Spring modules with Java. We will start with a simple application and in every section we will be adding one more Spring module to it.
If you are interested in how to do all the same things with XML configuration, this page can help you: Spring_MVC:_Tutorial
Spring MVC
[edit | edit source]- Create
.\pom.xml
: - Create
.\src\main\java\myapp\config\WebInit.java
: - Create
.\src\main\java\myapp\config\WebConfig.java
: - Create
.\src\main\java\myapp\controller\MainController.java
: mvn clean jetty:run
andcurl http://localhost:8080/raw
:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>mygroup</groupId>
<artifactId>SpringMvcJavaConfig</artifactId>
<version>0.1</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
<spring.version>6.0.3</spring.version>
<jetty-maven-plugin.version>11.0.12</jetty-maven-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty-maven-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
package myapp.config;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import jakarta.servlet.ServletRegistration;
import jakarta.servlet.ServletContext;
public class WebInit implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
var context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("myapp.config");
container.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = container
.addServlet("dispatcher", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
package myapp.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"myapp"})
public class WebConfig implements WebMvcConfigurer {
}
package myapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MainController {
@GetMapping("/raw")
@ResponseBody
public String greetRaw() {
return "hello there! UTF-8 не работает 😕";
}
}
hello there! UTF-8 ?? ???????? ?
Sources:
- zetcode.com: Three basic approaches to configure a Spring web application
- baeldung.com: web.xml vs Initializer
JSP
[edit | edit source]- Add this bean to
WebConfig.java
: - Add this method to
MainController.java
: - Create
.\src\main\webapp\WEB-INF\jsp\greeting.jsp
: - Visit http://localhost:8080/jsp.
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.context.annotation.Bean;
// ...
@Bean
InternalResourceViewResolver internalResourceViewResolver() {
var resolver = new InternalResourceViewResolver("/WEB-INF/jsp/", "");
resolver.setViewNames("*.jsp");
return resolver;
}
@GetMapping("/jsp")
public String greetJsp(ModelMap model) {
model.put("msg", "jsp сообщение 🪂");
return "greeting.jsp";
}
<%@page pageEncoding="UTF-8" %>
<html>
<head>
<title>Welcome</title>
</head>
<body>
<p>🚀 This is a complete ${msg}.</p>
</body>
</html>
Sources:
Thymeleaf
[edit | edit source]- Add this dependency to
pom.xml
: - Add this beans to
WebConfig.java
: - Add this method to
MainController.java
: - Create
.\src\main\webapp\WEB-INF\thy\hello.html
: - Visit
http://localhost:8080/thy
.
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.thymeleaf.templatemode.TemplateMode;
// ...
@Autowired
private ApplicationContext applicationContext;
@Bean
public SpringResourceTemplateResolver templateResolver(){
var templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/thy/");
templateResolver.setSuffix(".html");
templateResolver.setCacheable(false);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine(){
var templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver(){
var viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setCharacterEncoding("UTF-8");
// viewResolver.setOrder(1);
// viewResolver.setViewNames(new String[] {".html", ".xhtml"});
return viewResolver;
}
@GetMapping("/thy")
public String greetThy(ModelMap model) {
model.put("msg", "прыгает через ленивую собаку🐶");
return "hello.html";
}
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Welcome</title>
</head>
<body>
🦊The quick brown fox <label th:text="${msg}"></label>
</body>
</html>
Sources:
CSS
[edit | edit source]- Add this method to
WebConfig.java
: - Create
src\main\webapp\resources\css\styles.css
: - To your
greeting.jsp
/<html>/<head>
page add this line: - To your
hello.html
/<html>/<head>
add this line:
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
\\ ...
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/", "classpath:/static/");
}
body {
background-color: LightGrey;
}
<link rel="stylesheet" href="/resources/css/styles.css" />
<link rel="stylesheet" th:href="@{/resources/css/styles.css}" />
Now background of these pages will turn to grey.
Sources:
Spring Security
[edit | edit source]- Add these dependencies to your
pom.xml
: - Create
.\src\main\java\myapp\security\SecurityInitialization.java
: - Create
.\src\main\java\myapp\security\SecurityConfiguration.java
: - To register multiple users, just create new UserDetails and pass it to the constructor as well, it accepts varargs.
<spring-security.version>6.0.1</spring-security.version>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
package myapp.security;
import org.springframework.security.web.context.*;
public class SecurityInitialization extends AbstractSecurityWebApplicationInitializer {
public SecurityInitialization() { }
}
package myapp.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
// .requestMatchers("/jsp/**").hasRole("USER")
// .anyRequest().permitAll()
)
.httpBasic(withDefaults())
.formLogin(withDefaults());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("1234")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
Now you will have to log in to view any page of your web application.
Sources:
Spring Security + Thymeleaf
[edit | edit source]- To your
pom.xml
add this dependency: - In
WebConfig#templateEngine()
register new dialect: - To your
hello.html
/<html>/<body>
add this line: - Visit http://localhost:8080/thy, if you're logged in, your username will be displayed.
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
// ...
templateEngine.addDialect(new SpringSecurityDialect());
<p>Your name is <label th:text="${#authentication.name}" />, if I'm not mistaken. 🔑</p>
Sources:
Spring Security + JSP
[edit | edit source]- Add this dependency to your
pom.xml
: - Add this line above
<html>
tag ingreeting.jsp
: - Add this line in
greeting.jsp
/<html>/<body>
: Visit http://localhost:8080/jsp, if you're logged in your username will be displayed.
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring-security.version}</version>
</dependency>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<p>Here is your username: <sec:authentication property="name" />.</p>
Sources:
Logging
[edit | edit source]In any of your classes you can use this built-in logger right-away:
import org.apache.commons.logging.*;
// ...
private final Log log = LogFactory.getLog(getClass());
But if you'll want to use log4j2, you'll need to add it's library to dependencies and log4j2.xml
to classpath:
- Add this dependency to your
pom.xml
: - Create
.\src\main\resources\log4j2.xml
: - In any of your classes, use
org.apache.commons.*
package to write logs as with the built-in logger, Spring will now use log4j2 under the hood.
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="LogToConsoleFull" target="SYSTEM_OUT">
<PatternLayout pattern="%highlight{[%level] %logger.%method()}{trace=bright cyan} %msg%n"
disableAnsi="false" charset="866"/>
</Console>
<Console name="LogToConsole" target="SYSTEM_OUT">
<PatternLayout pattern="%highlight{[%-20.-40maxLen{%level] %logger{1}.%method()}{}}{trace=bright cyan} %msg%n"
disableAnsi="false" charset="866"/>
</Console>
<Console name="LogToConsoleBrief" target="SYSTEM_OUT">
<PatternLayout pattern="%highlight{[%level] %logger{1.}}{trace=bright cyan} %msg%n"
disableAnsi="false" charset="866"/>
</Console>
<Console name="LogToConsoleTiny" target="SYSTEM_OUT">
<PatternLayout pattern="%highlight{[%level]}{trace=bright cyan} %msg%n"
disableAnsi="false" charset="866"/>
</Console>
</Appenders>
<Loggers>
<Logger name="myapp" level="trace" additivity="false">
<AppenderRef ref="LogToConsoleTiny"/>
</Logger>
<Root level="info">
<AppenderRef ref="LogToConsoleTiny"/>
</Root>
</Loggers>
</Configuration>
But if you'll want to use log4j2 through slf4j interface, here's what you'll need to do:
- Remove
log4j-core
dependency if you have it; - Add this dependency higher than Thymeleaf dependency:
- In any of your classes, use slf4j interface:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.19.0</version>
</dependency>
import org.slf4j.*;
// ...
private final Logger log = LoggerFactory.getLogger(getClass());
Now you also can manage Thymeleaf logs as well, since it uses slf4j internally.
Sources:
Internationalization
[edit | edit source]- To
WebConfig.java
add these beans and an interceptor: - Create
src\main\resources\messages_en.properties
: - Create
src\main\resources\messages_ru.properties
: - In your Thymeleaf template
*.html
/<html>/<body>
add this line: - In your
greeting.jsp
add these lines: - Visit these pages and change language of message by appending
?lang=ru
or?lang=en
parameters to URL.
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import java.util.Locale;
// ...
@Bean
public ResourceBundleMessageSource messageSource() {
var messageSource = new ResourceBundleMessageSource();
messageSource.setDefaultEncoding("UTF-8");
messageSource.setBasename("messages");
return messageSource;
}
@Bean
public SessionLocaleResolver localeResolver() {
var localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH);
return localeResolver;
}
@Override
public void addInterceptors(final InterceptorRegistry registry) {
var localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
registry.addInterceptor(localeChangeInterceptor);
}
welcome=You are welcome!
welcome=Добро пожаловать!
<h2 th:text="#{welcome}">Welcome</h2>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<...>
<h2><spring:message code="welcome" /></h2>
Sources:
Aspects
[edit | edit source]- Add this dependency to your
pom.xml
: - To
WebConfig.java
add this annotation: - Create
.\src\main\java\myapp\aop\LoggingAspect.java
: - Your logging library may differ, consult Logging section on this page;
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
import org.springframework.context.annotation.EnableAspectJAutoProxy;
// ...
@EnableAspectJAutoProxy
public class WebConfig implements WebMvcConfigurer {
package myapp.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import org.slf4j.*;
@Aspect
@Component
public class LoggingAspect {
private final Logger log = LoggerFactory.getLogger(getClass());
@Around("execution(* *.controller.*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
log.trace("Before {} call", joinPoint.getSignature());
var obj = joinPoint.proceed();
log.trace("After {} call", joinPoint.getSignature());
return obj;
}
}
Now every invocation of every method from *.controller
package will be surrounded by corresponding log messages.
DataSource: H2 Embedded Database
[edit | edit source]Regardless of what technology you will choose for your application to interact with database, you will need a datasource bean. For convenience, this project uses embedded H2 database, and this section will show you how to enable it and get it's datasource bean.
- Add these dependencies to your
pom.xml
: - Add this bean to
WebConfig.java
: - Create
.\src\main\resources\schema.sql
: - Create
.\src\main\resources\data.sql
:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
</dependency>
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
// ...
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:data.sql")
.build();
}
create table if not exists Message (
id int auto_increment unique not null,
content varchar(255) not null
);
INSERT INTO message (content) VALUES
('Prune juice 🍇'),
('Буря мглою небо кроет...')
;
Now if you will start your application you will see a log:
Starting embedded database: url='jdbc:h2:mem:dataSource;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
Sources:
JdbcTemplate
[edit | edit source]JdbcTemplate doesn't need any explicit configuration. You'll need only spring-jdbc
dependency and datasource bean, which you've already got in previous section. This is an example of how to use JdbcTemplate:
- Create
.\src\main\java\myapp\model\Message.java
: - Create
.\src\main\java\myapp\service\MessageService.java
: - Create
src\main\java\myapp\repository\MessageJdbcDaoImpl.java
: - In your
MainController.java
add this imports, class variable and a method: - Visit https://localhost:8080/messages and you will see contents of your Message table. Create new message by sending it's content as request parameter: https://localhost:8080/messages/new?content=ThisShouldWork
package myapp.model;
public record Message(Long id, String content) {}
package myapp.service;
import myapp.model.Message;
import myapp.repository.MessageJdbcDaoImpl;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Component
public class MessageService {
@Autowired
private MessageJdbcDaoImpl messageJdbcDao;
public List<Message> findAll() {
return messageJdbcDao.findAll();
}
public void save(Message message) {
messageJdbcDao.save(message);
}}
package myapp.repository;
import org.springframework.stereotype.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import myapp.model.Message;
import javax.sql.DataSource;
import java.util.List;
@Repository
public class MessageJdbcDaoImpl {
JdbcTemplate jdbcTemplate;
private final RowMapper<Message> messageRowMapper = (resultSet, rowNum) -> {
return
new Message(resultSet.getLong("id"), resultSet.getString("content"));
};
@Autowired
public MessageJdbcDaoImpl(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Message> findAll() {
String query = "select * from Message";
List<Message> result =
jdbcTemplate.query(query, messageRowMapper);
return result;
}
public void save(Message message) {
var query = "INSERT INTO message (content) VALUES (?)";
jdbcTemplate.update(query, message.content());
}
}
import myapp.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import myapp.model.*;
import org.springframework.web.bind.annotation.RequestParam;
// ...
@Autowired
private MessageService messageService;
// ...
@GetMapping("/messages")
public String messages(ModelMap model) {
model.put("msg", messageService.findAll());
return "hello.html";
}
@GetMapping("/messages/new")
public String messageSave(@RequestParam(required=true) String content) {
messageService.save(new Message(null, content));
return "hello.html";
}
REST
[edit | edit source]- Add this dependency to
pom.xml
: - Add this method to
WebConfig.java
: - Create
.\src\main\java\myapp\rest\MainRest.java
: - In
SecurityConfiguration#securityFilterChain()
addcsrf().disable()
statement like that:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import java.util.List;
import java.nio.charset.Charset;
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
var messageConverter = new MappingJackson2HttpMessageConverter();
// messageConverter.setDefaultCharset(Charset.forName("UTF-8")); //doesn't help anyway
messageConverters.add(messageConverter);
}
package myapp.rest;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import myapp.model.*;
import myapp.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@RestController
@RequestMapping("/rest")
public class MainRest {
@Autowired
private MessageService messageService;
@GetMapping("/messages")
List<Message> messages() {
return messageService.findAll();
}
@PostMapping(value="/messages") //, consumes="application/json;charset=UTF-8"
void saveMessage(@RequestBody Message message) {
messageService.save(message);
}
}
http
.authorizeHttpRequests((authorize) -> authorize
// .anyRequest().authenticated()
// .requestMatchers("/jsp/**").hasRole("USER")
.anyRequest().permitAll()
)
.httpBasic(withDefaults())
.formLogin(withDefaults())
.csrf().disable();
return http.build();
Now http://localhost:8080/rest/messages will show all your messages in json format, and you can create new messages by sending them to the same address through curl or PowerShell:
curl -X POST --data '{"content":"in curl send latin text only"}' http://localhost:8080/rest/messages --header 'Content-Type: Application/Json' Invoke-WebRequest -Uri http://localhost:8080/rest/messages -Method POST -ContentType 'application/json;charset=utf-8' -Body '{"content":"In pwsh you can use all UTF8 characters 😀"}'
Hibernate Configuration
[edit | edit source]At this point you should have a working dataSource bean.
- Add these dependencies to
pom.xml
: - Add these beans and class-level annotation to
WebConfig.java
:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.0.CR1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.hibernate.SessionFactory;
// ...
@EnableTransactionManagement
// ...
@Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
var sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setPackagesToScan("myapp.model");
// sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
@Bean
public PlatformTransactionManager hibernateTransactionManager(SessionFactory sessionFactory) {
var transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory);
return transactionManager;
}
Now you can create Entity classes with annotations from jakarta.persistence
package and use SessionFactory or EntityManager in your dao layer:
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.EntityManager;
@PersistenceContext
EntityManager em;
// or...
import org.hibernate.SessionFactory;
@Autowired
SessionFactory;
Sources: