Spring Bean
-
Spring Bean là gì?
Như đã giới thiệu ở bài trước, Spring Bean là một đối tượng được quản lý bởi Spring IoC Container. Nói cách khác, thay vì tạo và quản lý đối tượng thủ công bằng từ khóa new, bạn để Spring tạo và quản lý vòng đời của đối tượng đó.
Một lớp Java bất kỳ có thể trở thành một Spring Bean nếu bạn khai báo nó trong cấu hình Spring hoặc chú thích nó bằng các annotation phù hợp như @Component, @Service, @Repository hoặc @Controller.
-
Bean hệ thống và Bean do người dùng định nghĩa
-
Bean hệ thống - Infrastructure Beans
Bean hệ thống là những Bean do chính Spring tạo ra phục vụ cho việc vận hành nội bộ của framework, ví dụ như xử lý AOP, transaction, annotation, web MVC, v.v. Bean hệ thống có các đặc điểm sau:
- Tự động được tạo ra bởi Spring khi bạn bật các chức năng như @EnableTransactionManagement, @EnableWebMvc, v.v.
- Không cần (và không nên) can thiệp hoặc sửa đổi trực tiếp.
- Có vai trò hỗ trợ cấu hình, xử lý lifecycle, proxying, injection, v.v.
Bean hệ thống thường có tên trong Container bắt đầu bằng internal hoặc org.springframework .*. Bảng dưới đây liệt kê một vài Bean thông dụng và vai trò của chúng.
Tên Bean Vai trò RequestMappingHandlerMapping Ánh xạ URL tới phương thức của Controller có @RequestMapping RequestMappingHandlerAdapter Kết nối phương thức của Controller với Request tương ứng ExceptionHandlerExceptionResolver Xử lý ngoại lệ thông qua các phương thức với @ExceptionHandler InternalResourceViewResolver Kết hợp với tên view tạo đường dẫn đầy đủ tới tập tin *.jsp, *.html, v.v. (/WEB-INF/views/*.jsp) AnnotationAwareAspectJAutoProxyCreator Tạo proxy tự động cho các Bean có @Aspect, hỗ trợ AOP TransactionInterceptor Quản lý giao dịch tự động PersistenceAnnotationBeanPostProcessor Xử lý các annotation như @PersistenceContext BeanNameViewResolver Tìm view dựa trên tên Bean CommonAnnotationBeanPostProcessor Xử lý @PostConstruct, @PreDestroy, @Resource, v.v. AutowiredAnnotationBeanPostProcessor Xử lý injection thông qua @Autowired và @Value ConfigurationClassPostProcessor Xử lý các lớp @Configuration, tạo và đăng ký các @Bean PropertySourcesPlaceholderConfigurer Đọc và inject giá trị từ application.properties hoặc yaml WebSocketHandlerMapping Ánh xạ WebSocket endpoint tới các WebSocketHandler được đăng ký trong WebSocketConfigurer DelegatingFilterProxy Cầu nối giữa filter của Container và SecurityFilterChain HandlerExceptionResolverComposite Tổng hợp các resolver để xử lý exception trong Controller Những Bean có tên chứa Handler, Adapter, Resolver, Processor, Proxy, Creator, Configurer thường là Bean hệ thống. Để xem các Bean hệ thống đang tồn tại trong Container, bạn có thể dùng phương thức getBeanDefinitionNames() của ApplicationContext interface như sau:
for (String beanName : context.getBeanDefinitionNames()) { System.out.println(beanName); }
-
Bean do người dùng định nghĩa - Application Beans/User-defined Beans
Bean do người dùng định nghĩa (User-defined Bean) là một đối tượng Java (thường là POJO - Plain Old Java Object) do lập trình viên tạo ra và đăng ký với Spring Container và do đó được Spring Container khởi tạo, cấu hình và quản lý. Bean này không phải là thành phần có sẵn của Spring Framework mà được định nghĩa bằng các annotation (@Component, @Service, @Repository, @Bean, v.v.) hoặc tập tin cấu hình XML.
Dưới đây chúng ta sẽ tìm hiểu ba phương pháp phổ biến để định nghĩa một Spring Bean.
-
Sử dụng annotation (@Component, @Service, @Repository, @Controller)
Trong Spring, bạn có thể chỉ định một lớp Java bằng các annotation đặc biệt để Spring tự động nhận diện và đăng ký lớp đó như một Bean thông qua cơ chế component scanning. Các annotation phổ biến được mô tả trong bảng dưới đây:
Annotation Vai trò @Component Đánh dấu Bean là một thành phần độc lập trong ứng dụng và có thể được sử dụng ở bất kỳ đâu @Repository Đánh dấu Bean thuộc tầng thao tác và truy vấn cơ sở dữ liệu (DAO) @Service Đánh dấu Bean thuộc tầng service (hoặc business logic) @Controller Đánh dấu Bean thuộc tầng web (controller) -
@Component
Annotation @Component đánh dấu một lớp là Spring Bean, thường được áp dụng cho các lớp helper/utility (như Email, File, Token, Logger, Encryptor) với mục đích tái sử dụng trong toàn bộ ứng dụng và dễ dàng tiêm vào các đối tượng khác thông qua cơ chế dependency injection của Spring.
Minh họa cách đánh dấu lớp AppLogger là một Bean bằng @Component
package vn.hoctotlamhay.learnspring.logger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class AppLogger { private static final Logger logger = LoggerFactory.getLogger(AppLogger.class); public void info(String message) { logger.info(message); } public void error(String message, Throwable exception) { logger.error(message, exception); } public void debug(String message) { logger.debug(message); } }
-
@Repository
Annotation @Repository dùng để đánh dấu một lớp thuộc tầng truy cập dữ liệu (DAO) là một Spring Bean. Khi đó, Spring sẽ quản lý lớp này bao gồm việc tự động chuyển các ngoại lệ cấp thấp (như JDBC, JPA) thành DataAccessException và cho phép dễ dàng tiêm vào các bean khác như @Service.
Minh họa cách đánh dấu interface ProductRepository là một Bean bằng @Repository.
package vn.hoctotlamhay.learnspring.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import vn.hoctotlamhay.learnspring.entity.Product; import java.util.List; @Repository public interface ProductRepository extends JpaRepository<Product, Long> { @Query("SELECT p FROM Product p") List<Product> findAllProducts(); @Query("SELECT p FROM Product p WHERE LOWER(p.name) LIKE LOWER(CONCAT('%', ?1, '%'))") List<Product> searchByName(String keyword); // Các truy vấn khác }
Chú ý
- Nếu lớp hoặc interface không làm nhiệm vụ truy xuất dữ liệu thì không nên dùng @Repository.
- Không dùng @Repository cho lớp xử lý logic nghiệp vụ.
- Không dùng @Repository cho lớp giao tiếp người dùng.
-
@Service
Annotation @Service đánh dấu một lớp thuộc tầng xử lý logic nghiệp vụ (business logic) là một Spring Bean. Các lớp này thường chịu trách nhiệm xử lý các yêu cầu từ các tầng khác như tầng repository hoặc controller.
Khi lớp được đánh dấu @Service, Spring sẽ tự động phát hiện và tạo đối tượng cho lớp đó, giúp dễ dàng tiêm (inject) vào các nơi khác trong ứng dụng.
Minh họa cách đánh dấu lớp ProductService là một Bean bằng @Service.
package vn.hoctotlamhay.learnspring.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import vn.hoctotlamhay.learnspring.entity.Product; import vn.hoctotlamhay.learnspring.repository.ProductRepository; import java.util.List; @Service public class ProductService { private final ProductRepository productRepository; @Autowired public ProductService(ProductRepository productRepository) { this.productRepository = productRepository; } public List<Product> getAllProducts() { return productRepository.findAllProducts(); } public List<Product> searchByName(String keyword) { return productRepository.searchByName(keyword); } // Các logic khác }
Chú ý
Trong một số trường hợp chúng ta không cần thiết chỉ định một lớp thuộc tầng xử lý logic nghiệp vụ là một Bean, ví dụ:
- Lớp chỉ chứa các phương thức tĩnh.
- Lớp là một đối tượng đơn giản (DTO) hoặc không có logic nghiệp vụ.
- Khi lớp không cần được quản lý bởi Spring IoC hoặc không cần tính năng tự động tiêm các phụ thuộc (dependency injection).
- Khi bạn muốn kiểm soát vòng đời của đối tượng.
-
@Controller
Annotation @Controller đánh dấu một lớp là một Spring MVC controller, có nhiệm vụ xử lý các yêu cầu từ máy khách, điều hướng đến view hoặc trả về dữ liệu (thường là JSON hoặc XML).
@Controller thường được dùng cùng với @RequestMapping, @GetMapping, @PostMapping, v.v. để ánh xạ các URL tới phương thức của lớp.
Minh họa cách đánh dấu lớp ProductController bằng annotation @Controller.
package vn.hoctotlamhay.learnspring.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import vn.hoctotlamhay.learnspring.entity.Product; import vn.hoctotlamhay.learnspring.service.ProductService; import java.util.List; @Controller public class ProductController { private final ProductService productService; @Autowired public ProductController(ProductService productService) { this.productService = productService; } @GetMapping("/products") public String viewAllProducts(Model model) { List<Product> productList = productService.getAllProducts(); model.addAttribute("products", productList); return "product-list"; } @GetMapping("/products/search") public String searchProducts(@RequestParam("keyword") String keyword, Model model) { List<Product> result = productService.searchByName(keyword); model.addAttribute("products", result); model.addAttribute("keyword", keyword); return "product-list"; } // Các phương thức khác }
Chú ý
- @Controller dùng để trả về view (giao diện HTML) chứ không phải dữ liệu.
- Nếu @Controller nằm ngoài package được @ComponentScan, Spring sẽ không phát hiện ra controller.
- Không sử dụng @Controller gắn với xử lý logic nghiệp vụ trực tiếp.
-
-
Sử dụng annotation @Bean
Annotation @Bean được sử dụng để khai báo một đối tượng là Spring Bean theo kiểu cấu hình Java thuần (Java-based Configuration). Điều này có nghĩa là @Bean phải được đặt trong một lớp có annotation @Configuration. Các đặc điểm nổi bật của Spring Bean khai báo bằng @Bean gồm:
- Mỗi phương thức có @Bean sẽ trả về một đối tượng được Spring quản lý.
- Tên Bean mặc định là tên phương thức nhưng có thể chỉ định cụ thể bằng @Bean(name = "beanName").
- Cho phép kiểm soát logic khởi tạo bean bao gồm cả việc thiết lập giá trị, phụ thuộc hoặc cấu hình nâng cao.
- Phạm vi mặc định là singleton nhưng có thể thay đổi với @Scope("prototype"), @Scope("request"), v.v.
- Bean được quản lý bởi ApplicationContext và có thể được sử dụng thông qua cơ chế dependency injection (@Autowired, constructor, setter hoặc field injection).
- Dễ kiểm soát với cấu hình phức tạp ngay cả khi không thể đánh dấu trực tiếp các lớp bằng @Component.
Minh họa phương pháp dùng @Bean để khai báo các Bean ModelMapper, PasswordEncoder và DateTimeFormatter.
package vn.hoctotlamhay.learnspring.configuration; import org.modelmapper.ModelMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import java.time.format.DateTimeFormatter; @Configuration public class CommonBeanConfig { @Bean public ModelMapper modelMapper() { return new ModelMapper(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public DateTimeFormatter dateFormatter() { return DateTimeFormatter.ofPattern("dd-MM-yyyy"); } }
-
-
-
Phạm vi của Spring Bean
Trong Spring, phạm vi của một Bean xác định cách Spring quản lý vòng đời và số lượng instance của Bean đó trong ApplicationContext (Container).
Khi khai báo một Bean bằng các annotation như @Component, @Repository, @Service hoặc bằng cấu hình Java thuần thông qua @Bean, phạm vi mặc định của Bean sẽ là singleton. Điều này có nghĩa là Spring sẽ tạo một instance duy nhất ngay khi khởi động Container và dùng duy nhất instance đó trong toàn bộ ứng dụng.
Nhìn chung, Spring Bean có thể có nhiều phạm vi, dưới đây là các phạm vi phổ biến:
Phạm vi Mô tả singleton (mặc định) Chỉ tạo một instance duy nhất trong toàn bộ Spring Container, dùng ở mọi nơi của ứng dụng prototype Mỗi lần yêu cầu Bean sẽ tạo một instance mới và không dùng chung request (chỉ dùng với web) Mỗi HTTP Request sẽ có một instance riêng session(chỉ dùng với web) Mỗi HTTP Session sẽ có một instance riêng application (chỉ dùng với web) Một instance được tạo cho toàn bộ ứng dụng web ServletContext websocket Mỗi WebSocket Session có một instance riêng Để chỉ định phạm vi của Spring Bean, chúng ta sử dụng annotation @Scope cùng với phạm vi cần chỉ định, ví dụ:
- Phạm vi singleton
- Phạm vi prototype
- Phạm vi request
@Bean @Scope("singleton") public ModelMapper modelMapper() { return new ModelMapper(); } Hoặc bỏ @Scope, vì singleton là mặc định: @Bean public ModelMapper modelMapper() { return new ModelMapper(); }
@Component @Scope("prototype") public class TransactionCodeGenerator { private final String code; public TransactionCodeGenerator() { this.code = UUID.randomUUID().toString(); } public String getCode() { return code; } }
@Bean @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public User user() { return new User(); }