Trước hết, kể từ phiên bản SpringBoot 2 - 888bets

Chủ đề này xuất phát từ một buổi chia sẻ của đồng nghiệp trong nhóm. Trước hết, kể từ phiên bản SpringBoot 2.x, cglib đã trở thành mặc định để thực hiện AOP. Dưới đây là cách cấu hình AopAutoConfiguration trong dự án cũ chạy trên phiên bản 1.x:

 1@Configuration
 2@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
 3@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
 4public class AopAutoConfiguration {
 5
 6    @Configuration
 7    @EnableAspectJAutoProxy(proxyTargetClass = false)
 8    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)
 9    public static class JdkDynamicAutoProxyConfiguration {}
10
11    @Configuration
12    @EnableAspectJAutoProxy(proxyTargetClass = true)
13    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
14    public static class CglibAutoProxyConfiguration {}
15}

Trong khi đó, ở phiên bản 2.x, cấu hình này đã thay đổi như sau:

 1@Configuration(proxyBeanMethods = false)
 2@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
 3public class AopAutoConfiguration {
 4
 5    @Configuration(proxyBeanMethods [mua the game w88 o dau](/post/fd1e1ad222e42158.html)  = false)
 6    @ConditionalOnClass(Advice.class)
 7    static class AspectJAutoProxyingConfiguration {
 8
 9        @Configuration(proxyBeanMethods = false)
10        @EnableAspectJAutoProxy(proxyTargetClass [keo toi nay](/post/44b0ec022b156fed.html)  = false)
11        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
12        static class JdkDynamicAutoProxyConfiguration {}
13
14        @Configuration(proxyBeanMethods = false)
15        @EnableAspectJAutoProxy(proxyTargetClass = true)
16        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
17        static class CglibAutoProxyConfiguration {}
18    }
19}

Tại sao AopAutoConfiguration được tải vào? Điều này đã được giải thích chi tiết trong bài viết trước về cơ chế tự động cấu hình của SpringBoot. Bạn có thể thấy rằng kể từ phiên bản 2.x, SpringBoot đã chuyển sang sử dụng cglib làm công cụ thực hiện dynamic proxy mặc định.

Vấn đề nổi lên

Giả sử chúng ta có đoạn mã đơn giản sau với SpringBoot và cơ sở dữ liệu, bao gồm cả annotation giao dịch (@Transactional):

 1@Mapper
 2public interface StudentMapper {
 3    @Insert("insert into student(name, age) values ('nick', '18')")
 4    Long insert();
 5}
 6
 7@Component
 8public class StudentManager {
 9    @Resource
10    private StudentMapper studentMapper;
11
12    public Long createStudent() {
13        return studentMapper.insert();
14    }
15}
16
17@Component
18public class StudentServiceImpl implements StudentService {
19    @Resource
20    private StudentManager studentManager;
21
22    @Resource
23    private StudentServiceImpl studentService;
24
25    @Override
26    @Transactional
27    public Long createStudent() {
28        Long id = studentManager.createStudent();
29        Long id2 = studentService.createStudent2();
30        return 1L;
31    }
32
33    @Transactional
34    private Long createStudent2() {
35        return studentManager.createStudent();
36    }
37}

Phương thức công khai createStudent gọi phương thức tạo sinh viên ở lớp manager, sau đó lại gọi tiếp đến createStudent2 thông qua tham chiếu tới chính nó (studentService). Khi chạy thử, chương trình báo lỗi NullPointerException tại vị trí studentManager là null trong phương thức createStudent2.

Lý do gây ra lỗi

Cglib thực hiện dynamic proxy bằng cách tạo một lớp con kế thừa từ đối tượng mục tiêu. Các logic cắt ngang (aspect) sẽ được xử lý trong lớp con này trước khi gọi đến phương thức mục tiêu gốc. Tuy nhiên, vấn đề xảy ra khi áp dụng cho các phương thức final hoặc private vì Java không cho phép override những phương thức này. Mặc dù vậy, trong trường hợp này, lớp con vẫn có thể truy cập vào phương thức riêng tư của lớp cha thông qua mechanism internal của JVM.

Do createStudent2 là phương thức private, nó không bị proxy bởi cglib mà chỉ đơn thuần được gọi trực tiếp như bình thường. Vì vậy, khi gọi phương thức này, nó không sử dụng bean được quản lý bởi Spring mà thay vào đó sử dụng chính lớp con được tạo ra bởi cglib. Kết quả là các dependency injection (như studentManager) không được áp dụng, dẫn đến lỗi NullPointerException.

Giải thích sâu hơn về cơ chế hoạt động

Xem xét đoạn mã sau từ lớp org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation:

1@Override
2protected Object invokeJoinpoint() throws Throwable {
3    if (this.methodProxy != null) {
4        return this.methodProxy.invoke(this.target, this.arguments);
5    } else {
6        return super.invokeJoinpoint();
7    }
8}

Đây là nơi methodProxy được sử dụng để gọi phương thức trên đối tượng đích (target object), tức là bean được quản lý bởi Spring. Nếu không có cơ chế này, các phương thức không được proxy sẽ không thể hoạt động đúng như mong đợi.

Quá trình khởi tạo proxy

Spring thiết lập đối tượng đích trong quá trình khởi tạo bean thông qua giao diện org.springframework.beans.factory.config.BeanPostProcessor. Phương thức postProcessAfterInitialization được gọi sau khi bean được khởi tạo:

 1@Override
 2public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
 3    if (bean != null) {
 4        Object cacheKey = getCacheKey(bean.getClass(), beanName);
 5        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
 6            return wrapIfNecessary(bean, beanName, cacheKey);
 7        }
 8    }
 9    return bean;
10}

Logic cụ thể nằm trong phương thức wrapIfNecessary của lớp AbstractAutoProxyCreator:

 1protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
 2    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
 3        return bean;
 4    }
 5    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
 6        return bean;
 7    }
 8    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
 9        this.advisedBeans.put(cacheKey, Boolean.FALSE);
10        return bean;
11    }
12    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
13    if (specificInterceptors != DO_NOT_PROXY) {
14        this.advisedBeans.put(cacheKey, Boolean.TRUE);
15        Object proxy = createProxy(
16                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
17        this.proxyTypes.put(cacheKey, proxy.getClass());
18        return proxy;
19    }
20    this.advisedBeans.put(cacheKey, Boolean.FALSE);
21    return bean;
22}

Cuối cùng, proxy được tạo ra trong phương thức createProxy của AbstractAutoProxyCreator.

Kết luận: Để tránh lỗi tương tự, cần đảm bảo rằng các phương thức yêu cầu proxy phải là public và không được đánh dấu là final.