https://andifalk.github.io/
spring-basics-training/ presentation/index.html
This workshop is a hands-on workshop, so you have to make your hands “dirty”!!
Requirements for completing the labs:
Use any Java IDE (Eclipse, IntelliJ, NetBeans, VS Code)
Recommended ones are:
All hands-on related code is on master branch at https://github.com/andifalk/spring-basics-training
You find lots of labs with two sub directories
Rod Johnson had an idea and wrote a book:
“J2EE Design & Development”
First source code available
Published Spring 1.0
Java 1.3 & 1.4 compatible
Spring 2.x
Java 5 & 6 compatible
Java EE 5 compliant
Spring 3.x
Java 6 & 7 compatible
Java EE 6 compliant
Rod Johnson leaves Sping
Founded Pivotal
Spring 4.x
Java 7 & 8 compatible
Java EE 6 & 7 compliant
Published Spring Boot 1.0 (2014)
Spring 5.x
Spring Boot 2.0
Java 8+ compatible
Java EE 7+ compliant
Spring Auto-Configuration
“Convention over Configuration”
Build a working Spring App in 5 minutes
Successor of Spring XD
Toolkit for building data integration and real-time data processing pipelines
Pipelines consist of Spring Boot apps
Patterns for Cloud-Native Applications
Configuration management
Service discovery
Circuit breakers
Implementation of well-known EAI Patterns
Integration with External Systems
ReST/HTTP, Twitter, EMail, JMS, FTP, ...
Developing Kafka-based messaging solutions
Adds Kafka support to Spring Integration
Developing batch processing applications
Chunk based processing
Start/Stop/Restart
Retry/Skip
Servlet-based Web (Spring MVC)
Reactive Web (Spring WebFlux)
Restful services
Thymeleaf, JSF, Tapestry, ...
JDBC based repositories
This is NOT an ORM
Support for @Query annotations
Enhanced support for JPA based data access layers
Supports Hibernate, EclipseLink as JPA providers
Pagination support, dynamic query execution
Data access for MongoDB document database
Mongo Template support
POJO centric model for interacting with a MongoDB DBCollection
Redis Template support
Repository for key/value store
Core spring functionality
Application context & Beans
Dependency Injection
“Legacy”
Supports Java 6 / 7
EOL: End of 2016
Long term version
Supports Java 6 / 7 / 8
EOL: 2020
Current version
Supports Java 8 / 9 / 10+
Authentication
SSO (OAuth2, OpenID Connect)
Authorization
Encryption
Reactive library for building non-blocking applications
Based on Reactive Streams Specification
Base library for Spring WebFlux (Spring 5)
Don't reinvent CI/CD pipelines for each project
Provide templates with best practices for common deployment pipelines
Supports Jenkins and Concourse CI
Lightweight development with POJOs
Loose coupling through Dependency Injection
Single Responsibility Principle
Open/Closed Principle
Liskov's Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Depend on abstractions, not on concretions
Service Locator
Dependency Injection
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Inject
public void setMyService(MyService myService) {
...
}
@Autowired
public void setMyService(MyService myService) {
...
}
XML
Java Configuration
Annotations (Component Scan)
public interface BeanOne {
String whoAmI();
}
public class BeanOneImpl implements BeanOne {
private final BeanTwo beanTwo;
public BeanOneImpl(BeanTwo beanTwo) {
this.beanTwo = beanTwo;
}
@Override
public String whoAmI() {
return BeanOneImpl.class.getSimpleName()
+ ": " + beanTwo.whoAmI();
}
}
<beans>
<bean id="beanOne" class="com.example.beans.BeanOneImpl">
<constructor-arg ref="beanTwo"/>
</bean>
<bean id="beanTwo" class="com.example.beans.BeanTwoImpl">
</bean>
</beans>
public class XmlApplication {
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext(
"application.xml");
BeanOne beanOne = ctx.getBean(BeanOne.class);
beanOne.whoAmI();
}
}
public interface BeanOne {
String whoAmI();
}
public class BeanOneImpl implements BeanOne {
private final BeanTwo beanTwo;
@Autowired
public BeanOneImpl(BeanTwo beanTwo) {
this.beanTwo = beanTwo;
}
@Override
public String whoAmI() {
return BeanOneImpl.class.getSimpleName()
+ ": " + beanTwo.whoAmI();
}
}
@Configuration
public class ApplicationConfiguration {
@Bean
public BeanOne beanOne() {
return new BeanOneImpl(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwoImpl(...);
}
}
public class JavaApplication {
public static void main(String[] args) {
ApplicationContext ctx
= new AnnotationConfigApplicationContext(
ApplicationConfiguration.class);
BeanOne beanOne = ctx.getBean(BeanOne.class);
beanOne.whoAmI();
}
}
public interface BeanOne {
String whoAmI();
}
@Component
public class BeanOneImpl implements BeanOne {
private final BeanTwo beanTwo;
@Autowired
public BeanOneImpl(BeanTwo beanTwo) {
this.beanTwo = beanTwo;
}
@Override
public String whoAmI() {
return BeanOneImpl.class.getSimpleName()
+ ": " + beanTwo.whoAmI();
}
}
@ComponentScan(basePackages = "com.example")
@Configuration
public class ApplicationConfiguration {
}
public class JavaApplication {
public static void main(String[] args) {
ApplicationContext ctx
= new AnnotationConfigApplicationContext(
ApplicationConfiguration.class);
BeanOne beanOne = ctx.getBean(BeanOne.class);
beanOne.whoAmI();
}
}
@Component
@Configuration
@Service
@Repository
@Controller
@RestController
@Transactional
@Service
@Target(TYPE)
@Retention(RUNTIME)
@Documented
public @interface TransactionalService {
...
@AliasFor(annotation = Transactional.class,
attribute = "propagation")
Propagation propagation() default Propagation.REQUIRED;
}
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
Singleton
Prototype
Request
Session
Global Session
Application
Declarative programming
Eliminating boilerplate code
Great testing support
Aspect-Oriented Programming (AOP)
Transaction Management
Security
Logging
@Service
public class TaskService {
...
@Transactional(propagation = Propagation.REQUIRES_NEW)
@PreAuthorize("hasRole('USER')")
public Task createTask(
...
}
...
}
@Before
@AfterReturning
@AfterThrowing
@After
@Around
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.0</version>
</dependency>
@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringComponentsApplication {
public static void main(String[] args) {
SpringApplication.run(
SpringComponentsApplication.class, args);
}
}
@Component
@Aspect
public class LoggingAspect {
static final Logger LOG = LoggerFactory.getLogger(
LoggingAspect.class);
@Around("execution(* person.PersonService.*(..))")
public Object logAction (ProceedingJoinPoint pjp)
throws Throwable {
LOG.info("Calling {}.{}",
pjp.getTarget().getClass().getName(),
pjp.getSignature().getName());
return proceedingJoinPoint.proceed();
}
}
public List<Person> findAll() {
List<Person> result = new ArrayList<>();
try (Connection conn = dataSource.getConnection()) {
statement = conn.prepareStatement(
"select * from person");
rs = statement.executeQuery();
while (rs.next()) {
person = new Person();
…
result.add(person);
}
} catch (SQLException e) {…} finally {}
return result;
}
public List<Person> findAll() {
return jdbcTemplate.query("select * from person",
new RowMapper<Person>() {
@Override
public Person mapRow(ResultSet arg0, int arg1)
throws SQLException {
Person person = new Person();
…
return person;
}
});
}
public List<Person> findAll() {
return jdbcTemplate.query("select * from person",
(rs, rowNum) -> {
Person person = new Person();
…
return person;
});
}
TransactionTemplate
JmsTemplate
RestTemplate
OAuth2RestTemplate
...
public interface Storage {
}
@Component
public class Harddisk implements Storage {
}
@Component
public class CompactDisc implements Storage {
}
@Service
public class BackupManager {
private Storage storage;
@Autowired
public void setStorage(Storage storage) {
this.storage = storage;
}
...
}
NoUniqueBeanDefinitionException:
No qualifying bean of type 'com.example.beans.Storage'
available: expected single matching bean but found 2:
compactDisc,harddisk
at org.springframework.beans.factory.config.
DependencyDescriptor.resolveNotUnique
at org.springframework.beans.factory.support.
DefaultListableBeanFactory.doResolveDependency
...
@Primary
@Qualifier
@Profile
@Conditional
@Primary
public interface Storage {
}
@Primary @Component
public class Harddisk implements Storage {
}
@Component
public class CompactDisc implements Storage {
}
@Service
public class BackupManager {
private Storage storage;
@Autowired
public void setStorage(Storage storage) {
this.storage = storage;
}
...
}
@Qualifier
public interface Storage {
}
@Qualifier("HD") @Component
public class Harddisk implements Storage {
}
@Qualifier("CD") @Component
public class CompactDisc implements Storage {
}
@Service
public class BackupManager {
private Storage storage;
@Qualifier("CD") @Autowired
public void setStorage(Storage storage) {
this.storage = storage;
}
...
}
-Dspring.profiles.active="dev"
public interface Storage {
}
@Profile("dev") @Component
public class Harddisk implements Storage {
}
@Profile("prod") @Component
public class CompactDisc implements Storage {
}
@Service
public class BackupManager {
private Storage storage;
@Autowired
public void setStorage(Storage storage) {
this.storage = storage;
}
...
}
@Conditional
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {...}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {...}
package org.springframework.boot.autoconfigure.orm.jpa;
@Configuration
@ConditionalOnClass({ ..., EntityManager.class })
@Conditional(HibernateEntityManagerCondition.class)
public class HibernateJpaAutoConfiguration {...}
<bean id="numberGuess" class="com.example.NumberGuess">
<property
name="randomNumber"
value="#{T(java.lang.Math).random() * 100.0}"/>
</bean>
<bean id="shapeGuess" class="com.example.ShapeGuess">
<property
name="initialShapeSeed"
value="#{ numberGuess.randomNumber }"/>
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(
"T(java.lang.Math).random() * 100.0");
double value = (Double) expression.getValue();
Spring In Action 4th Edition
(Manning, ISBN: 978-1617291203)
https://start.spring.io
https://start.spring.io/spring.zip
$ spring init -d=web,security,jpa --build=gradle my-dir
Using service at https://start.spring.io
Project extracted to '/home/my-user/my-dir'
package org.springframework.boot.autoconfigure;
...
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
...
}
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(
MyApplication.class, args);
}
}
@Configuration
@ConditionalOnClass(Flyway.class)
@ConditionalOnBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.flyway",
name = "enabled", matchIfMissing = true)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class })
public class FlywayAutoConfiguration {
...
}
@SpringBootApplication (
exclude = DataSourceAutoConfiguration.class)
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(
MyApplication.class, args);
}
}
spring.application.name=My App
spring.main.show-banner=false
server.port=8080
spring.datasource.jdbc-url=jdbc:h2:mydb
spring.datasource.username=sa
spring:
application:
name: My App
main:
show-banner: false
datasource:
jdbc-url: jdbc:h2:mydb
username: sa
YAML=YAML Ain't Markup Language
application.properties or application.yml (default)
application-dev.properties (if “dev” profile is active)
application-cloud.yml (if “cloud” profile is active)
@ConfigurationProperties(prefix = "hosts")
@Validated
public class MyProperties {
private Host host;
...
public static class Host {
@NotNull
private String ip;
@Min(1)
private int port;
...
}
}
hosts:
host:
ip: 192.168.0.1
port: 8080
Can be packaged as...
Executable JAR or as... (Executable) WARbut...
Maven
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.2.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
Gradle
plugins {
id 'org.springframework.boot' version '2.0.2.RELEASE'
}
HTTP
JMX
Endpoint | Description | Enabled |
---|---|---|
beans | List of Spring beans | Yes |
conditions | Evaluated auto-config | Yes |
configprops | List of properties | Yes |
health | Application health info | Yes |
metrics | ‘Metrics’ information | Yes |
shutdown | Gracefully shutdown app | No |
threaddump | Performs a thread dump | Yes |
http://localhost:8080/actuator/health
{
"status": "UP",
"details": {
"mongo": {
"status": "UP",
"details": {
"version": "3.2.2"
}
},
"diskSpace": {
"status": "UP",
"details": {
...
}}}}
@Component
public class BlobStorageHealthEndpoint
implements HealthIndicator {
private CloudBlobClient cloudBlobClient;
...
@Override
public Health health() {
Health.Builder builder = Health.up();
...
return builder.build();
}
}
@Service
public class PersonServiceImpl implements PersonService {
@Autowired private PersonRepository personRepository;
@Override
public List<Person> findAll() {
return personRepository.findAll();
}
}
@Entity
public class Person
extends AbstractPersistable<Long> {
private String firstName;
private String lastName;
private String yearOfBirth;
public Person() {
super();
}
…
}
public class SpringSecurityAuditorAware
implements AuditorAware<User> {
@Override
public User getCurrentAuditor() {
Authentication authentication =
SecurityContextHolder
.getContext ().getAuthentication ();
…
}
}
@Entity
public class Person
extends AbstractAuditable<User,Long> {
private String firstName;
private String lastName;
private String yearOfBirth;
…
}
Create/Read/Update/Delete
public interface CrudRepository<T, ID>
extends Repository<T, ID> {
...
<S extends T> S save(S entity);
Optional<T> findById(ID id);
Iterable<T> findAll();
long count();
boolean existsById(ID id);
void delete(T entity);
...
}
findByLastName()
findByLastNameAndFirstName()
findByFirstNameIgnoreCaseOrderByFirstName()
…
public interface PersonRepository extends
JpaRepository<Person, Long> {
...
@Query("SELECT p FROM #{#entityName} p LEFT JOIN FETCH "
+ "p.addresses WHERE p.id = :id")
Person findOneWithAddresses(@Param("id") Long id);
}
Separation between presentation and model
Controller and view communicate through the model
Observer pattern to update views on model changes
https://martinfowler.com/eaaDev/uiArchs.html#ModelViewController
“A controller that handles all requests for a Web site”
@Controller
public class HomeController {
@RequestMapping(value="/", method=GET)
public String home() {
return "home";
}
}
@RequestMapping(
path="/",
method = POST
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(
path="/",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(
path="/",
produces = MediaType.APPLICATION_JSON_VALUE)
Indicates a method return value should be bound to the web response body
@RestController =
@Controller + @ResponseBody
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
...
}
Thymeleaf
JSP & JSTL
Jackson (JSON, XML)
Script Views (e.g. React)
PDF, Excel
...
JavaServer Faces (JSF)
Apache Struts
Tapestry
Authentication
Authorization
Password Encoders
Security Testing Support
Support for Reactive Web Applications
Support for OAuth 2.0 Client
Support for OpenID Connect 1.0, JWT and JOSE (JWS/JWE/JWK) Client
Authentication required for all HTTP endpoints
Session Fixation Protection
Session Cookie (HttpOnly, Secure)
CSRF Protection
Security Response Headers
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2018-05-10 20:11:24.505 INFO 28867 ---
[main] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password:
4cb48dca-1c78-4dbb-85bc-a5200256105e
GET / HTTP/1.1
Host: localhost:8080
HTTP/1.1 401
WWW-Authenticate: Basic realm="hello"
GET / HTTP/1.1
Host: localhost:8080
Authorization: Basic dXNlcjpzZWNyZXQ=
POST /login HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=14965E3A995DA1973F42F308D59727D4
username=user&password=secret&submit=Login
HTTP/1.1 302
Set-Cookie: JSESSIONID=49C632387800316021BE804AB2F27C15;
Path=/; HttpOnly
GET / HTTP/1.1
Host: localhost:8080
Cookie: JSESSIONID=49C632387800316021BE804AB2F27C15
public class UserDetailsConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories
.createDelegatingPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.builder().username("user@example.com")
.password(passwordEncoder().encode("secret"))
.roles("USER").build()
);
}}
class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(
AuthenticationManagerBuilder auth) {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) {
http.httpBasic().and().formLogin()
.and().authorizeRequests()
.antMatchers("/unrestricted").permitAll()
.anyRequest().authenticated();
}}
package org.springframework.security.crypto.password;
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword,
String encodedPassword);
}
public class PasswordEncoderFactories {
public static PasswordEncoder
createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders
= new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
return new DelegatingPasswordEncoder(
encodingId, encoders);
}}
Passwords are stored this way
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQ...
GET / HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiI
Session Cookie | Token (Bearer, JWT) |
---|---|
With each Request (on same domain) |
Manually as Header |
Potential CSRF! | No CSRF possible |
One domain | Cross domain (CORS) |
Sensitive Info (HTTPS) | Sensitive Info (HTTPS) |
server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=another-secret
...Header
...Payload
...Signature
GET / HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1N...
Header
{
typ: "JWT",
alg: "RS256"
}
Payload
{
iss: "https://identity.example.com",
aud: "my-client-id",
exp: 1495782385,
nonce: "N0.46824857243233511495739124749",
iat: 1495739185,
at_hash: "hC1NDSB8WZ9SnjXTid175A",
sub: "mysubject",
auth_time: 1495739185,
email: "test@gmail.com"
}
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
JSR 250: e.g. @RolesAllowed
Spring Security: @Secured
Method Security Expressions: e.g. @PreAuthorize
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig
extends WebSecurityConfigurerAdapter {
...
}
public class UserBoundaryService {
@PreAuthorize("hasRole('ADMIN')")
public List<User> findAllUsers() {...}
}
public class TaskBoundaryService {
@PreAuthorize("hasPermission(#taskId, 'TASK', 'WRITE')")
public Task findTask(UUID taskId) {...}
}
public class AuthorizationIntegrationTest {
@WithMockUser(roles = "ADMIN")
@Test
public void verifyFindAllUsersAuthorized() {...}
@WithMockUser(roles = "USER")
@Test(expected = AccessDeniedException.class)
public void verifyFindAllUsersUnauthorized() {...}
}
Iron-Clad Java: Building Secure Web Applications (Oracle Press, ISBN: 978-0071835886)
White Box Test
Runs Fast (only some milliseconds)
Tests Public API methods in a single class
Uses Mocking
Repeatable (with same results)
Isolated/Independent
May Run Parallelized
public class HelloWorldTest {
private HelloWorld cut;
@Before // --> Arrange
public void setUp() {
cut = new HelloWorld();
}
@Test
public void verifySayHello() {
String greeting = cut.sayHello("World"); // --> Act
assertThat("Should have got expected greeting",
greeting, is("Hallo World")); // --> Assert
} }
import static org.hamcrest.Matchers.*;
assertThat(frodo.getName(), is(equalTo(myBiscuit)));
assertThat(frodo.getName(), is(not(equalTo(myBiscuit))));
assertThat(frodo.getName(), startsWith("Fro"));
assertThat(frodo.getName(), endsWith("do"));
assertThat(frodo.getName(),
is(equalToIgnoringCase("frodo")));
assertThat(fellowshipOfTheRing.size()), is(equalTo(9));
assertThat(fellowshipOfTheRing), hasItems(frodo, sam));
assertThat(fellowshipOfTheRing), not(hasItem(sauron));
import static org.assertj.core.api.Assertions.*;
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron);
assertThat(frodo.getName()).startsWith("Fro")
.endsWith("do")
.isEqualToIgnoringCase("frodo");
assertThat(fellowshipOfTheRing).hasSize(9)
.contains(frodo, sam)
.doesNotContain(sauron);
Dummy
Fake
Mock
Spy
Simple API
Provides Mock and Spy
Supports Call Verification
Also mocks final/static methods (Mockito 2)
@RunWith(MockitoJUnitRunner.class)
public class PersonServiceMockTest {
@Mock
private PersonDao personDao;
@InjectMocks
private PersonService cut;
@Test
public void verifySomething() {
when(personDao.findAll()).thenReturn(
Collections.singletonList(
new Person("firstName", "lastName")));
...
}
}
Tests WITHOUT Spring-Container
Mocks for PropertySource, Servlets, Reactive Web, ...
ReflectionUtils
Integrated test of multiple units
Medium runtime (minutes)
Tests using external systems
Partly uses mocking
Tests WITH Spring-Container
SpringJUnitRunner
Caching for Spring Contexts
Dependency Injection
Mocked Beans
@RunWith(SpringJUnitRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@TestPropertySource(properties=
{"timezone = GMT", "port: 8081"})
@Sql({"/test-schema.sql", "/test-user-data.sql"})
@Transactional
public class ApplicationTests {
@Autowired
private ShopManagementService cut;
@Test
public void verifySomething() { cut.doSomething(); }
…
}
@WebMvcTest
@WebFluxTest
@DataJpaTest
@DataMongoTest
@RestClientTest
...
Cleanup (“Boy Scout Rule”)
Code Comprehension
Preparation For New Feature
Planned (Long-Term) Refactoring
Infrastructure Updates
JUnit 5 Testing Support
Functional Container Extensions
Reactive Systems / WebFlux
Java 8 Baseline
Java 9 Automatic Modules
Java EE 7 Baseline
Java EE 8 Support (Servlet 4, Bean Validation 2.0)
HTTP/2 Support (Servlet 4)
Jetty 9.4
Tomcat 8.5
Hibernate 5.2
Hikari Connection Pool
Test Context Support for JUnit 4 and JUnit 5
@SpringJUnitConfig = @ExtendWith(SpringExtension.class) + @ContextConfiguration
GenericApplicationContext ctx
= new GenericApplicationContext();
// Default constructor via reflection
ctx.registerBean(First.class);
// Explicit constructor via Supplier
ctx.registerBean(Second.class,
() -> new Second(ctx.getBean(First.class)));
// Explicit constructor with BeanDefinition customization
ctx.registerBean(Third.class,
() -> new Third(ctx.getBean(First.class)),
bd -> bd.setLazyInit(true));