Spring Exception
1. Spring BeanDefinitionStoreException
Trong mục này, chúng ta sẽ cùng nhau thảo luận về Spring org.springframework.beans.factory.BeanDefinitionStoreException - đây thường là trách nhiệm của BeanFactory khi định nghĩa bean không hợp lệ hay việc tải bean đó có vấn đề. Để tìm hiểu rõ về Spring Exception này, chúng ta hãy tìm hiểu các nguyên nhân phổ biến nhất và từ đó đưa ra các giải pháp thích hợp cho từng nguyên nhân.
Nguyên nhân: java.io.FileNotFoundException
Có nhiều nguyên nhân có thể xảy ra BeanDefinitionStoreException có thể do IOException cơ bản gây ra:
- XML phân tích cú pháp IOException từ ServletContext Resource:
Exception này thường xảy ra trong ứng dụng Web Spring, khi một DispatcherServlet được thiết lập trong web.xml cho Spring MVC:
1
2
3
4
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
Theo mặc định, Spring sẽ tìm kiếm một file có tên chính xác là springMvcServlet-servlet.xml trong thư mục /WEB-INF của ứng dụng web.
Nếu file này không tồn tại, thì ngoại lệ sau sẽ được ném ra:
1
2
3
4
org.springframework.beans.factory.BeanDefinitionStoreException:
Ioexception Parsing Xml Document from Servletcontext Resource [/WEB-INF/mvc-servlet.xml];
nested exception is java.io.FileNotFoundException:
Could not open ServletContext resource [/WEB-INF/mvc-servlet.xml]
Tất nhiên, giải pháp của chúng ta là đảm bảo rằng file mvc-servlet.xml thực sự tồn tại trong /WEB-INF; nếu không, thì một file xml đơn giản có thể được tạo nhau sau:
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd" >
</beans>
- IOException Parsing XML Document From Class Path Resource: Điều này thường xảy ra khi một cái gì đó trong ứng dụng trỏ đến một tài nguyên XML không tồn tại hoặc không được đặt đúng vị trí của nó.
Việc trỏ đến một tài nguyên như vậy có thể xảy ra theo nhiều cách khác nhau. Sử dụng cho ví dụ Java Configuration, ta có thể cấu hình trông giống như sau:
1
2
3
@Configuration
@ImportResource("beans.xml")
public class SpringConfig {...}
Trong XML, nó sẽ là:
1
<import resource="beans.xml"/>
Hoặc thậm chí bằng cách tạo ngữ cảnh Spring XML theo cách thủ công như:
1
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Tất cả những điều này sẽ dẫn đến cùng một ngoại lệ nếu file không tồn tại:
1
2
3
4
org.springframework.beans.factory.BeanDefinitionStoreException:
Ioexception Parsing Xml Document from Servletcontext Resource [/beans.xml];
nested exception is java.io.FileNotFoundException:
Could not open ServletContext resource [/beans.xml]
Nguyên nhân: Không thể giải quyết Trình giữ chỗ (Resolve Placeholder)
Lỗi này sẽ xảy ra khi Spring cố gắng giải quyết một thuộc tính nhưng không thể - vì một trong nhiều lý do có thể sau đây. Nhưng trước hết, việc sử dụng thuộc tính - chúng có thể được sử dụng trong file XML:
1
... value="${some.property}" ...
Thuộc tính cũng có thể được sử dụng trong code Java:
1
2
@Value("${some.property}")
private String someProperty;
Điều đầu tiên cần kiểm tra là tên của thuộc tính có thực sự khớp với thuộc tính được định nghĩa hay không; trong ví dụ này, chúng ta cần xác định thuộc tính sau:
1
some.property = someValue
Sau đó, chúng ta cần kiểm tra xem file thuộc tính được xác định ở đâu trong Spring – việc này được mô tả chi tiết trong Spring Property mà chúng ta đã tìm hiểu ở phần trước. Một phương pháp hay nhất để làm theo là có tất cả các file thuộc tính trong thư mục /src/main/resources của ứng dụng và tải chúng lên thông qua:
1
"classpath:app.properties"
Chuyển từ điều hiển nhiên - một nguyên nhân khác có thể khiến Spring không thể giải quyết thuộc tính là có thể có nhiều bean PropertyPlaceholderConfigurer trong ngữ cảnh Spring (hoặc nhiều phần tử thuộc tính-placeholder).
Nếu đúng như vậy, thì giải pháp là thu gọn những thuộc tính này thành một thuộc tính duy nhất hoặc cấu hình nó trong ngữ cảnh cha với ignoreUnresolvablePlaceholder.
Nguyên nhân: java.lang.NoSuchMethodError
Lỗi này có nhiều dạng - một trong những dạng phổ biến hơn là:
1
2
3
4
5
org.springframework.beans.factory.BeanDefinitionStoreException:
Unexpected exception parsing XML document from ServletContext resource [/WEB-INF/mvc-servlet.xml];
nested exception is java.lang.NoSuchMethodError:
org.springframework.beans.MutablePropertyValues.add (Ljava/lang/String;Ljava/lang/Object;)
Lorg/springframework/beans/MutablePropertyValues;
Lỗi này thường xảy ra khi có nhiều phiên bản Spring trên classpath. Có một phiên bản cũ hơn của Spring vô tình trên classpath của dự án phổ biến hơn người ta nghĩ. Giải pháp cho lỗi này rất đơn giản - hãy kiểm tra tất cả các thư viện Spring-jar trên classpath và đảm bảo rằng tất cả chúng đều có cùng một phiên bản - và phiên bản đó là 3.0 trở lên.
Tương tự, ngoại lệ không bị hạn chế đối với bean MutablePropertyValues - có một số hiện thân khác của cùng một vấn đề, được gây ra bởi sự không nhất quán của cùng một phiên bản:
1
2
3
4
org.springframework.beans.factory.BeanDefinitionStoreException:
Unexpected exception parsing XML document from class path resource [/WEB-INF/mvc-servlet.xml];
- nested exception is java.lang.NoSuchMethodError:
org.springframework.util.ReflectionUtils.makeAccessible(Ljava/lang/reflect/Constructor;)
Nguyên nhân: java.lang.NoClassDefFoundError
Một vấn đề phổ biến, tương tự liên quan đến Maven và các phụ thuộc Spring dependency hiện có là:
1
2
3
4
org.springframework.beans.factory.BeanDefinitionStoreException:
Unexpected exception parsing XML document from ServletContext resource [/WEB-INF/mvc-servlet.xml];
nested exception is java.lang.NoClassDefFoundError:
org/springframework/transaction/interceptor/TransactionInterceptor
Điều này xảy ra khi chức năng giao dịch (transactional functionality) được định cấu hình trong cấu hình XML:
1
<tx:annotation-driven/>
NoClassDefFoundError có nghĩa là hỗ trợ Spring Transactional - cụ thể là spring-tx - không tồn tại trên classpath. Giải pháp rất đơn giản - spring-tx cần được xác định trong Maven pom:
1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
Tất nhiên, điều này không giới hạn ở chức năng giao dịch - một lỗi tương tự sẽ xảy ra nếu thiếu AOP:
1
2
3
4
Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException:
Unexpected exception parsing XML document from class path resource [/WEB-INF/mvc-servlet.xml];
nested exception is java.lang.NoClassDefFoundError:
org/aopalliance/aop/Advice
Các file jar hiện được yêu cầu là: spring-aop (và ngầm định là aopalliance):
1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
Các bạn có thể thấy, ở phần cuối của Spring BeanDefinitionStoreException, chúng ta đã có một bản đồ rõ ràng để điều hướng nhiều nguyên nhân và sự cố có thể dẫn đến Bean Definition Store Exception cũng như nắm bắt tốt cách khắc phục tất cả các sự cố đó. Còn một vài Exception quan trọng khác sẽ được chia sẻ đến bạn trong phần tiếp theo.
2. Spring BeanCreationException
Trong mục này, chúng ta sẽ thảo luận về Spring org.springframework.beans.factory.BeanCreationException - đây là một ngoại lệ rất phổ biến được ném ra khi BeanFactory tạo các bean của các định nghĩa bean và gặp sự cố. Chúng ta sẽ tìm hiểu về các nguyên nhân phổ biến nhất của ngoại lệ này cùng với giải pháp của nó.
Nguyên nhân: org.springframework.beans.factory.NoSuchBeanDefinitionException
Cho đến nay, nguyên nhân phổ biến nhất của BeanCreationException là Spring cố gắng inject một bean không tồn tại trong ngữ cảnh (context).
Ví dụ: BeanA đang cố gắng inject BeanB vào:
1
2
3
4
5
6
7
@Component
public class BeanA {
@Autowired
private BeanB dependency;
...
}
Nếu không tìm thấy BeanB trong ngữ cảnh, thì ngoại lệ sau sẽ được ném ra (Lỗi tạo Bean):
1
2
3
4
5
6
7
Error creating bean with name 'beanA': Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field: private com.baeldung.web.BeanB com.thecorleone.web.BeanA.dependency;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.thecorleone.web.BeanB] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Để chẩn đoán loại sự cố này - trước tiên, hãy đảm bảo rằng bean được khai báo:
- trong file cấu hình XML sử dụng phần tử
- hoặc trong lớp Java
@Configurationthông qua chú thích@Bean - hoặc được chú thích bằng:
@Component,@Repository,@Service,@Controllervà scan classpath đang hoạt động cho package đó. Đồng thời kiểm tra xem các file cấu hình hoặc lớp có thực sự được Spring chọn và tải vào ngữ cảnh chính hay không.
Nguyên nhân: org.springframework.beans.factory.NoUniqueBeanDefinitionException
Một nguyên nhân tương tự khác cho ngoại lệ tạo bean là Spring đang cố gắng inject một bean theo loại - cụ thể là bởi interface của nó và tìm thấy hai hoặc nhiều bean thực hiện interface đó trong cùng ngữ cảnh.
Ví dụ: BeanB1 và BeanB2 đều triển khai cùng một interface:
1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class BeanB1 implements IBeanB { ... }
@Component
public class BeanB2 implements IBeanB { ... }
@Component
public class BeanA {
@Autowired
private IBeanB dependency;
...
}
Điều này sẽ dẫn đến trường hợp ngoại lệ sẽ bị Spring bean ném ra như sau:
1
2
3
4
5
6
Error creating bean with name 'beanA': Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field: private com.baeldung.web.IBeanB com.thecorleone.web.BeanA.b;
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [com.thecorleone.web.IBeanB] is defined:
expected single matching bean but found 2: beanB1,beanB2
Nguyên nhân: org.springframework.beans.BeanInstantiationException
- Custom Exception
Tiếp theo là một bean ném một ngoại lệ trong quá trình tạo nó; một mẫu đơn giản hóa để dễ dàng minh họa và hiểu được vấn đề đang đưa ra một ngoại lệ trong constructor của bean:
1
2
3
4
5
6
7
8
9
@Component
public class BeanA {
public BeanA() {
super();
throw new NullPointerException();
}
...
}
Như dự đoán, việc này sẽ dẫn đến việc Spring nhanh chóng ném ngoại lệ sau:
1
2
3
4
5
Error creating bean with name 'beanA' defined in file [...BeanA.class]:
Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException:
Could not instantiate bean class [com.thecorleone.web.BeanA]:
Constructor threw exception;
nested exception is java.lang.NullPointerException
- java.lang.InstantiationException
Một trường hợp có thể xảy ra khác của BeanInstantiationException - định nghĩa một lớp trừu tượng là một bean trong XML - phải ở dạng XML, vì không có cách nào để thực hiện việc này trong fille Java @Configuration và quá trình scan classpath sẽ bỏ qua lớp trừu tượng:
1
2
@Component
public abstract class BeanA implements IBeanA { ... }
Và định nghĩa XML của bean:
1
<bean id="beanA" class="com.thecorleone.web.BeanA" />
Thiết lập này sẽ dẫn đến một ngoại lệ tương tự:
1
2
3
4
5
6
7
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'beanA' defined in class path resource [beansInXml.xml]:
Instantiation of bean failed;
nested exception is org.springframework.beans.BeanInstantiationException:
Could not instantiate bean class [com.thecorleone.web.BeanA]:
Is it an abstract class?;
nested exception is java.lang.InstantiationException
- java.lang.NoSuchMethodException
Nếu một bean không có constructor mặc định và Spring cố gắng khởi tạo nó bằng cách tìm kiếm constructor đó, điều này sẽ dẫn đến ngoại lệ thời gian chạy (runtime exception) - ví dụ:
1 2 3 4 5 6 7 8
@Component public class BeanA implements IBeanA { public BeanA(final String name) { super(); System.out.println(name); } }
Khi bean này được chọn bởi cơ chế scan classpath, lỗi sẽ là:
1
2
3
4
5
Error creating bean with name 'beanA' defined in file [...BeanA.class]: Instantiation of bean failed;
nested exception is org.springframework.beans.BeanInstantiationException:
Could not instantiate bean class [com.thecorleone.web.BeanA]:
No default constructor found;
nested exception is java.lang.NoSuchMethodException: com.baeldung.web.BeanA.<init>()
Một ngoại lệ tương tự nhưng khó chẩn đoán hơn, có thể xảy ra khi các Spring dependency trên classpath không có cùng phiên bản; loại không tương thích phiên bản này có thể dẫn đến NoSuchMethodException do các thay đổi API. Giải pháp cho vấn đề này là đảm bảo tất cả các thư viện Spring có cùng một phiên bản chính xác trong dự án.
Nguyên nhân: org.springframework.beans.NotWoritesPropertyException
Tuy nhiên, một khả năng khác đang xác định một bean là BeanA - với một tham chiếu đến một bean khác là BeanB - mà không có phương thức setter tương ứng trong BeanA:
1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class BeanA {
private IBeanB dependency;
...
}
@Component
public class BeanB implements IBeanB { ... }
Cấu hình Spring XML:
<bean id="beanA" class="com.thecorleone.web.BeanA">
<property name="beanB" ref="beanB" />
</bean>
Một lần nữa, vấn đề này chỉ có thể xảy ra trong cấu hình XML, vì khi sử dụng Java @Configuration, trình biên dịch sẽ làm cho vấn đề này không thể tái tạo. Tất nhiên, để giải quyết vấn đề này, setter cần được thêm vào IBeanB:
1
2
3
4
5
6
7
8
@Component
public class BeanA {
private IBeanB dependency;
public void setDependency(final IBeanB dependency) {
this.dependency = dependency;
}
}
Nguyên nhân: org.springframework.beans.factory.CannotLoadBeanClassException
Ngoại lệ này được đưa ra khi Spring không thể tải lớp của bean đã xác định - điều này có thể xảy ra nếu Cấu hình XML của Spring có chứa bean mà không có lớp tương ứng. Ví dụ: nếu lớp BeanZ không tồn tại, định nghĩa sau sẽ dẫn đến một ngoại lệ:
1
<bean id="beanZ" class="com.thecorleone.web.BeanZ" />
Nguyên nhân gốc rễ nếu ClassNotFoundException và ngoại lệ đầy đủ trong trường hợp này là:
1
2
3
4
5
6
nested exception is org.springframework.beans.factory.BeanCreationException:
...
nested exception is org.springframework.beans.factory.CannotLoadBeanClassException:
Cannot find class [com.thecorleone.web.BeanZ] for bean with name 'beanZ'
defined in class path resource [beansInXml.xml];
nested exception is java.lang.ClassNotFoundException: com.thecorleone.web.BeanZ
Children of BeanCreationException
- Org.springframework.beans.factory.BeanCurrentlyInCreationException
Một trong những lớp con của BeanCreationException là BeanCurrentlyInCreationException; điều này thường xảy ra khi sử dụng constructor injection - ví dụ: trong trường hợp circular dependency:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class BeanA implements IBeanA {
private IBeanB beanB;
@Autowired
public BeanA(final IBeanB beanB) {
super();
this.beanB = beanB;
}
}
@Component
public class BeanB implements IBeanB {
final IBeanA beanA;
@Autowired
public BeanB(final IBeanA beanA) {
super();
this.beanA = beanA;
}
}
Spring sẽ không thể giải quyết loại kịch bản wired này và kết quả cuối cùng sẽ là:
1
2
3
org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'beanA':
Requested bean is currently in creation: Is there an unresolvable circular reference?
Ngoại lệ đầy đủ của nó rất dài:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'beanA' defined in file [...BeanA.class]:
Unsatisfied dependency expressed through constructor argument with index 0
of type [com.thecorleone.web.IBeanB]: :
Error creating bean with name 'beanB' defined in file [...BeanB.class]:
Unsatisfied dependency expressed through constructor argument with index 0
of type [com.thecorleone.web.IBeanA]: :
Error creating bean with name 'beanA': Requested bean is currently in creation:
Is there an unresolvable circular reference?;
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'beanA':
Requested bean is currently in creation:
Is there an unresolvable circular reference?;
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'beanB' defined in file [...BeanB.class]:
Unsatisfied dependency expressed through constructor argument with index 0
of type [com.thecorleone.web.IBeanA]: :
Error creating bean with name 'beanA':
Requested bean is currently in creation:
Is there an unresolvable circular reference?;
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'beanA':
Requested bean is currently in creation: Is there an unresolvable circular reference?
- org.springframework.beans.factory.BeanIsAbstractException
Ngoại lệ khởi tạo này có thể xảy ra khi Bean Factory cố gắng truy xuất và khởi tạo một bean được khai báo là trừu tượng; ví dụ:
1
2
3
public abstract class BeanA implements IBeanA {
...
}
Cấu hình XML được khai báo như sau:
1
<bean id="beanA" abstract="true" class="com.thecorleone.web.BeanA" />
Bây giờ, nếu chúng ta cố gắng truy xuất BeanA từ Spring Context theo tên - ví dụ khi khởi tạo một bean khác:
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class Config {
@Autowired
BeanFactory beanFactory;
@Bean
public BeanB beanB() {
beanFactory.getBean("beanA");
return new BeanB();
}
}
Kết quả sẽ dẫn đến ngoại lệ sau:
1
2
3
4
5
6
7
8
9
10
11
12
org.springframework.beans.factory.BeanIsAbstractException:
Error creating bean with name 'beanA': Bean definition is abstract
Và stacktrace ngoại lệ đầy đủ như sau:
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'beanB' defined in class path resource
[org/thecorleone/spring/config/WebConfig.class]: Instantiation of bean failed;
nested exception is org.springframework.beans.factory.BeanDefinitionStoreException:
Factory method
[public com.thecorleone.web.BeanB com.thecorleone.spring.config.WebConfig.beanB()] threw exception;
nested exception is org.springframework.beans.factory.BeanIsAbstractException:
Error creating bean with name 'beanA': Bean definition is abstract
Chúng ta đã có một bản đồ rõ ràng để điều hướng nhiều nguyên nhân và sự cố có thể dẫn đến BeanCreationException trong Spring cũng như nắm bắt tốt cách khắc phục tất cả các sự cố này.
3. Spring NoSuchBeanDefinitionException
Trong mục này, chúng ta sẽ thảo luận về Spring org.springframework.beans.factory.NoSuchBeanDefinitionException - đây là một ngoại lệ phổ biến do BeanFactory ném ra khi cố gắng giải quyết một bean đơn giản là không được định nghĩa trong Spring Context.
Chúng ta sẽ minh họa các nguyên nhân có thể gây ra sự cố này và các giải pháp hiện có.
Nguyên nhân: Không có Bean đủ điều kiện thuộc loại […] Được tìm thấy vì Dependency
(No Qualifying Bean of Type […] Found for Dependency)
Nguyên nhân phổ biến nhất của ngoại lệ này chỉ đơn giản là cố gắng inject một bean chưa được xác định. Ví dụ - BeanB đang wired trong việc cộng tác với BeanA:
1
2
3
4
5
6
7
@Component
public class BeanA {
@Autowired
private BeanB dependency;
//...
}
Bây giờ, nếu dependency - BeanB - không được định nghĩa trong Spring Context thì quá trình bootstrap sẽ không thành công với không có ngoại lệ định nghĩa bean nào như vậy:
1
2
3
4
5
6
7
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.thecorleone.packageB.BeanB]
found for dependency:
expected at least 1 bean which qualifies as
autowire candidate for this dependency.
Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
Lý do được Spring chỉ ra rõ ràng: “expected at least 1 bean which qualifies as autowire candidate for this dependency”
Một lý do khiến BeanB có thể không tồn tại trong ngữ cảnh - nếu các bean được chọn tự động bằng cách scan classpath và nếu BeanB được chú thích chính xác là bean (@Component, @Repository, @Service, @Controller, ...) - thì có thể được xác định trong một package không được scan bởi Spring:
1
2
3
4
5
6
7
8
9
10
package com.thecorleone.packageB;
@Component
public class BeanB { ...}
Trong khi đó, việc scan classpath được cấu hình như sau:
@Configuration
@ComponentScan("com.thecorleone.packageA")
public class ContextWithJavaConfig {
...
}
Nếu bean không được tự động scan bởi cách xác định theo cách thủ công thì BeanB đơn giản là không được xác định trong Spring Context hiện tại.
Nguyên nhân: Trường […] trong […] Yêu cầu loại Bean […] Không thể tìm thấy
(Cause: Field […] in […] Required a Bean of Type […] That Could Not Be Found)
Hãy lấy cùng một ví dụ trong đó BeanB có wired trong BeanA nhưng nó không được định nghĩa:
1
2
3
4
5
6
7
@Component
public class BeanA {
@Autowired
private BeanB dependency;
//...
}
Nếu chúng ta cố gắng chạy ứng dụng đơn giản này, ứng dụng đó sẽ cố tải BeanA:
1
2
3
4
5
6
7
@SpringBootApplication
public class NoSuchBeanDefinitionDemoApp {
public static void main(String[] args) {
SpringApplication.run(NoSuchBeanDefinitionDemoApp.class, args);
}
}
Ứng dụng sẽ không khởi động được với thông báo lỗi:
1
2
3
4
5
6
7
8
9
10
11
12
***************************
APPLICATION FAILED TO START
***************************
Description:
Field dependency in com.thecorleone.springbootmvc.nosuchbeandefinitionexception.BeanA required a bean of type 'com.thecorleone.springbootmvc.nosuchbeandefinitionexception.BeanB' that could not be found.
Action:
Consider defining a bean of type 'com.thecorleone.springbootmvc.nosuchbeandefinitionexception.BeanB' in your configuration.
Ở đây, com.thecorleone.springbootmvc.nosuchbeandefinitionexception là package cho BeanA, BeanB và NoSuchBeanDefinitionDemoApp.
Nguyên nhân: Không có loại Bean nào đủ điều kiện […] được xác định
(Cause: No Qualifying Bean of Type […] Is Defined)
Một nguyên nhân khác cho ngoại lệ là sự tồn tại của hai định nghĩa bean trong ngữ cảnh, thay vì một. Ví dụ: nếu một interface - IBeanB được thực hiện bởi hai bean - BeanB1 và BeanB2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class BeanB1 implements IBeanB {
// code something
}
@Component
public class BeanB2 implements IBeanB {
// code something
}
Bây giờ, nếu BeanA autowired interface này, Spring không biết sẽ inject vào một trong hai triển khai nào:
@Component
public class BeanA {
@Autowired
private IBeanB dependency;
// ...
}
Và một lần nữa, điều này sẽ dẫn đến một NoSuchBeanDefinitionException được ném bởi BeanFactory:
1
2
3
4
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type
[com.thecorleone.packageB.IBeanB] is defined:
expected single matching bean but found 2: beanB1,beanB2
Một giải pháp cho vấn đề này là sử dụng chú thích @Qualifier để chỉ định chính xác tên của bean mà chúng ta muốn wired:
1
2
3
4
5
6
7
8
@Component
public class BeanA {
@Autowired
@Qualifier("beanB2")
private IBeanB dependency;
...
}
Bây giờ Spring đã có đủ thông tin để đưa ra quyết định sẽ inject vào bean nào - BeanB1 hoặc BeanB2 (tên mặc định của BeanB2 là beanB2).
Nguyên nhân: Không có Bean nào được đặt tên […] được xác định
(Cause: No Bean Named […] Is Defined)
Một NoSuchBeanDefinitionException cũng có thể được ném ra khi một bean không được xác định được yêu cầu bằng tên từ ngữ cảnh Spring:
1
2
3
4
5
6
7
8
9
10
11
@Component
public class BeanA implements InitializingBean {
@Autowired
private ApplicationContext context;
@Override
public void afterPropertiesSet() {
context.getBean("someBeanName");
}
}
Trong trường hợp này, không có định nghĩa bean nào cho “someBeanName” - dẫn đến ngoại lệ sau:
1
2
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No bean named 'someBeanName' is defined
Một lần nữa, Spring chỉ ra rõ ràng và ngắn gọn lý do của sự thất bại trong trường hợp này: “No bean named X is defined”.
Nguyên nhân: Bean uỷ quyền
(Cause: Proxied Beans)
Khi một bean trong ngữ cảnh được ủy quyền bằng cơ chế JDK Dynamic Proxy thì proxy sẽ không mở rộng bean đích (tuy nhiên, nó sẽ triển khai các interface giống nhau).
Do đó, nếu bean được inject bởi một interface, nó sẽ được bao bọc chính xác hơn. Tuy nhiên, nếu bean được inject bởi lớp thực, thì Spring sẽ không tìm thấy định nghĩa bean khớp với lớp - vì proxy không thực sự mở rộng lớp.
Một lý do rất phổ biến khiến bean có thể bị proxied là hỗ trợ giao dịch Spring - cụ thể là bean được chú thích bằng @Transactional. Ví dụ: nếu ServiceA inject ServiceB vào và cả hai dịch vụ đều là giao dịch, thì việc inject theo định nghĩa lớp sẽ không hoạt động:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private ServiceB serviceB;
...
}
@Service
@Transactional
public class ServiceB implements IServiceB{
...
}
Hai service giống nhau, lần này được inject chính xác bởi interface, sẽ OK:
@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private IServiceB serviceB;
...
}
@Service
@Transactional
public class ServiceB implements IServiceB{
...
}
Hướng dẫn này đã thảo luận các ví dụ về các nguyên nhân phổ biến có thể xảy ra đối với NoSuchBeanDefinitionException - tập trung vào cách giải quyết các ngoại lệ này trong thực tế.
4. Unsatisfied Dependency trong Spring
Trong mục hướng dẫn này, chúng ta sẽ giải thích ngoại lệ UnsatisfiedDependencyException của Spring, nguyên nhân gây ra nó và cách phòng tránh nó.
Cause of UnsatisfiedDependencyException (không thoã mãn Dependency)
Như tên của nó ta cũng có thể biết được UnsatisfiedDependencyException được ném khi một số bean dependency hoặc thuộc tính không được thỏa mãn. Điều này có thể xảy ra khi ứng dụng Spring cố gắng wired một bean và không thể giải quyết một trong những dependency bắt buộc.
- Example Application - Ứng dụng mẫu
Giả sử chúng ta có một lớp dịch vụ
PurchaseDeptService, lớp này phụ thuộc vàoInventoryRepository: ```java @Service public class PurchaseDeptService { public PurchaseDeptService(InventoryRepository repository) { this.repository = repository; } }
public interface InventoryRepository { }
@Repository public class ShoeRepository implements InventoryRepository { }
@SpringBootApplication public class SpringDependenciesExampleApplication {
1
2
3
public static void main(String[] args) {
SpringApplication.run(SpringDependenciesExampleApplication.class, args);
} } ```
Hiện tại, chúng ta sẽ giả định rằng tất cả các lớp này nằm trong cùng một gói có tên thecorleone.dependency.exception.app.
Khi chúng ta chạy ứng dụng Spring này, mọi thứ đều hoạt động tốt. Hãy xem loại sự cố nào chúng ta có thể gặp phải nếu bỏ qua bước cấu hình.
- Component Annotation Missing – Thiếu chú thích Component
Bây giờ, hãy xóa chú thích @Repository khỏi lớp ShoeRepository của chúng ta:
1 2 3
public class ShoeRepository implements InventoryRepository { // code in here }
Khi chúng ta khởi động lại ứng dụng của mình, sẽ thấy thông báo lỗi sau: “UnsatisfiedDependencyException: Error creating bean with name ‘purchaseDeptService’: Unsatisfied dependency expressed through constructor parameter 0”.
Trường hợp này, Spring không được hướng dẫn để wired ShoeRepository bean và thêm nó vào ngữ cảnh ứng dụng, do đó không thể inject nó và ném ngoại lệ.
Nhưng yên tâm, việc thêm lại chú thích @Repository vào ShoeRepository sẽ giúp chúng ta giải quyết được vấn đề một cách đơn giản và dễ dàng.
- Package Not Scanned – Gói chưa được Scan
Bây giờ, hãy đặt
ShoeRepositorycủa chúng ta (cùng vớiInventoryRepository) vào một package riêng biệt có tênthecorleone.dependency.exception.repository.
Một lần nữa, khi chúng ta chạy ứng dụng của mình, nó sẽ ném ra ngoại lệ UnsatisfiedDependencyException. Để giải quyết điều này, chúng ta có thể cấu hình package scan trên package cha và đảm bảo rằng tất cả các lớp có liên quan đều được include:
1
2
3
4
5
6
7
@SpringBootApplication
@ComponentScan(basePackages = {"com.thecorleone.dependency.exception"})
public class SpringDependenciesExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDependenciesExampleApplication.class, args);
}
}
- Non-unique Dependency Resolution – Giải pháp Dependency không duy nhất
Giả sử chúng ta thêm một triển khai InventoryRepository khác - DressRepository:
1
2
3
@Repository
public class DressRepository implements InventoryRepository {
}
Bây giờ, khi chúng ta chạy ứng dụng - nó sẽ một lần nữa ném ra ngoại lệ UnsatisfiedDependencyException. Tuy nhiên, lần này tình hình đã khác. Khi nó xảy ra, dependency không thể được giải quyết khi có nhiều hơn một bean thỏa mãn nó.
Để giải quyết vấn đề này, chúng ta có thể muốn thêm @Qualifier để phân biệt giữa các repository:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Qualifier("dresses")
@Repository
public class DressRepository implements InventoryRepository {
}
@Qualifier("shoes")
@Repository
public class ShoeRepository implements InventoryRepository {
}
Ngoài ra, chúng ta sẽ phải thêm một Qualifier vào dependency của PurchaseDeptService constructor:
public PurchaseDeptService(@Qualifier("dresses") InventoryRepository repository) {
this.repository = repository;
}
Việc làm này sẽ làm cho DressRepository trở thành lựa chọn khả thi duy nhất và Spring sẽ đưa nó vào PurchaseDeptService.
Trong mục này, chúng ta đã thấy một số trường hợp phổ biến nhất UnsatisfiedDependencyException. Ngoài ra, chúng ta đã học cách giải quyết những vấn đề này một cách chi tiết.
Ngoại lệ nói chung là phần không thể tránh khỏi với những lập trình viên Java. Tuy nhiên, nếu bạn đủ kiến thức thì việc tránh nó là chuyện dễ dàng, nhưng cuộc sống không phải lúc nào cũng như ý – do đó trang bị cho mình các hiểu biết về cách phòng tránh và khắc phục ngoại lệ là thực sự cần thiết. Nếu không may gặp phải ngoại lệ trong quá trình run-code, đừng hoảng hốt mà hay đọc kỹ Exception đó và tìm cách giải quyết phù hợp – bạn có thể search từ khoá ngoại lệ đó trên Google hoặc xem lại bài viết này 😊))
Bài viết mang tính chất “ghi chú, lưu trữ, chia sẻ và phi lợi nhuận”.
Nếu bạn thấy hữu ích, đừng quên chia sẻ với bạn bè và đồng nghiệp của mình nhé!
Happy coding! 😎 👍🏻 🚀 🔥
Đọc thêm: