Spring Boot Advanced Features
1. Auto Configuration
1.1 Custom Auto Configuration
@Configuration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(DatabaseProperties.class)
public class DatabaseAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DatabaseProperties properties) {
return DataSourceBuilder
.create()
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.build();
}
}
@ConfigurationProperties(prefix = "app.database")
public class DatabaseProperties {
private String url;
private String username;
private String password;
// Getters and setters
}
// META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.DatabaseAutoConfiguration
1.2 Conditional Configuration
// Custom Condition
public class OnCustomCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("custom.property");
}
}
@Conditional(OnCustomCondition.class)
@Configuration
public class CustomConfiguration {
// Configuration based on condition
}
// Built-in Conditions
@ConditionalOnProperty(prefix = "app", name = "feature")
@ConditionalOnBean(DataSource.class)
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.data.redis.RedisClient")
@ConditionalOnExpression("${app.feature.enabled:true}")
@Profile("dev")
2. Custom Starter
2.1 Starter Structure
my-spring-boot-starter/
├── pom.xml
├── src/main/java/
│ └── com/example/starter/
│ ├── MyAutoConfiguration.java
│ ├── MyProperties.java
│ └── MyService.java
└── src/main/resources/
└── META-INF/
└── spring.factories
2.2 Implementation
// Auto Configuration
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties properties) {
return new MyService(properties);
}
}
// Properties
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
private boolean enabled = true;
private String prefix = "default";
// Getters and setters
}
// Service
public class MyService {
private final MyProperties properties;
public MyService(MyProperties properties) {
this.properties = properties;
}
public String doSomething() {
if (!properties.isEnabled()) {
return null;
}
return properties.getPrefix() + ": Done";
}
}
3. Event Handling
3.1 Application Events
// Custom Event
public class UserRegisteredEvent extends ApplicationEvent {
private final User user;
public UserRegisteredEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
}
// Event Publisher
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void registerUser(User user) {
// Business logic
eventPublisher.publishEvent(new UserRegisteredEvent(this, user));
}
}
// Event Listeners
@Component
public class UserEventListener {
@EventListener
public void handleUserRegistration(UserRegisteredEvent event) {
// Handle event
}
@EventListener
@Async
public void handleAsyncEvent(UserRegisteredEvent event) {
// Handle event asynchronously
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleTransactionalEvent(UserRegisteredEvent event) {
// Handle event after transaction commits
}
}
4. Custom Actuator Endpoints
4.1 Custom Endpoint
@Component
@Endpoint(id = "custom")
public class CustomEndpoint {
@ReadOperation
public Map<String, Object> getCustomInfo() {
Map<String, Object> info = new HashMap<>();
info.put("timestamp", System.currentTimeMillis());
info.put("status", "UP");
return info;
}
@WriteOperation
public void setCustomInfo(@Selector String name,
@Selector String value) {
// Update operation
}
@DeleteOperation
public void deleteCustomInfo(@Selector String name) {
// Delete operation
}
}
4.2 Health Indicator
@Component
public class CustomHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Builder builder) throws Exception {
try {
// Perform health check
builder.up()
.withDetail("app", "Healthy")
.withDetail("time", System.currentTimeMillis());
} catch (Exception e) {
builder.down()
.withDetail("error", e.getMessage());
}
}
}
5. Testing
5.1 Unit Testing
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void whenFindById_thenReturnUser() {
// Given
User user = new User();
when(userRepository.findById(1L))
.thenReturn(Optional.of(user));
// When
User found = userService.findById(1L);
// Then
assertNotNull(found);
verify(userRepository).findById(1L);
}
}
5.2 Integration Testing
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void whenCreateUser_thenStatus201() throws Exception {
User user = new User("test@test.com");
String json = objectMapper.writeValueAsString(user);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.email").value("test@test.com"));
}
}
6. Security
6.1 JWT Authentication
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtTokenProvider tokenProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JwtAuthenticationFilter(tokenProvider),
UsernamePasswordAuthenticationFilter.class
);
}
}
@Component
public class JwtTokenProvider {
@Value("${app.jwtSecret}")
private String jwtSecret;
@Value("${app.jwtExpirationInMs}")
private int jwtExpirationInMs;
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal =
(UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public Long getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
public boolean validateToken(String authToken) {
try {
Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(authToken);
return true;
} catch (SignatureException |
MalformedJwtException |
ExpiredJwtException |
UnsupportedJwtException |
IllegalArgumentException ex) {
return false;
}
}
}
6.2 OAuth2
@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login()
.authorizationEndpoint()
.baseUri("/oauth2/authorize")
.and()
.redirectionEndpoint()
.baseUri("/oauth2/callback/*")
.and()
.userInfoEndpoint()
.userService(customOAuth2UserService)
.and()
.successHandler(oAuth2AuthenticationSuccessHandler)
.failureHandler(oAuth2AuthenticationFailureHandler);
}
}
@Service
public class CustomOAuth2UserService
extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest)
throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(oAuth2UserRequest);
try {
return processOAuth2User(
oAuth2UserRequest,
oAuth2User
);
} catch (Exception ex) {
throw new OAuth2AuthenticationProcessingException(ex.getMessage());
}
}
private OAuth2User processOAuth2User(
OAuth2UserRequest oAuth2UserRequest,
OAuth2User oAuth2User) {
// Process OAuth2 user information
return CustomUserPrincipal.create(user, oAuth2User.getAttributes());
}
}
7. Spring Boot Caching
Cache Configuration
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("users"),
new ConcurrentMapCache("roles")
));
return cacheManager;
}
}
Using Cache
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// This will be cached
return userRepository.findById(id).orElse(null);
}
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
8. Spring Boot Messaging
RabbitMQ Configuration
@Configuration
public class RabbitConfig {
@Bean
public Queue queue() {
return new Queue("messageQueue", false);
}
@Bean
public TopicExchange exchange() {
return new TopicExchange("messageExchange");
}
@Bean
public Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("routing.key");
}
}
Message Producer and Consumer
@Service
public class MessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String message) {
rabbitTemplate.convertAndSend("messageExchange", "routing.key", message);
}
}
@Service
public class MessageConsumer {
@RabbitListener(queues = "messageQueue")
public void receiveMessage(String message) {
// Process message
System.out.println("Received message: " + message);
}
}
9. Spring Boot Microservices
Service Discovery với Eureka
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
@SpringBootApplication
@EnableEurekaClient
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
Circuit Breaker với Resilience4j
@RestController
public class ServiceController {
@CircuitBreaker(name = "backendA", fallbackMethod = "fallback")
@GetMapping("/backend")
public String backendA() {
return restTemplate.getForObject("http://backend-a", String.class);
}
public String fallback(Exception e) {
return "Fallback response";
}
}
10. Best Practices
1. Configuration Management
- Sử dụng application.yml thay vì application.properties
- Sử dụng profiles cho các môi trường khác nhau
- Externalize configuration
2. Exception Handling
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(
new Date(),
ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
}
3. Logging
@Slf4j
@Service
public class UserService {
public User createUser(User user) {
log.info("Creating user: {}", user.getEmail());
try {
return userRepository.save(user);
} catch (Exception e) {
log.error("Error creating user: {}", e.getMessage());
throw e;
}
}
}
4. Security Best Practices
- Sử dụng HTTPS
- Implement rate limiting
- Validate input data
- Sử dụng secure headers
- Implement CORS policy