CFSDN nhấn mạnh vào giá trị tạo ra nguồn mở và chúng tôi cam kết xây dựng nền tảng chia sẻ tài nguyên để mọi nhân viên CNTT có thể tìm thấy thế giới tuyệt vời của bạn tại đây.
Bài viết trên blog CFSDN này giải quyết vấn đề không thể khởi động dự án có thể làm mới khi bật cấu hình Spring-Cloud-OpenFeign. Nó được tác giả sưu tầm và biên soạn. Nếu bạn quan tâm đến bài viết này, hãy nhớ thích nó.

Bài viết này liên quan đến thiết kế và nguyên tắc cơ bản cũng như vị trí vấn đề. Nó tương đối sâu và dài nên được chia thành hai bài:
- Trên: Mô tả ngắn gọn về vấn đề và nguyên tắc của Spring Cloud RefreshScope
- Next: Các lỗi hiện tại do spring-cloud-openfeign + spring-cloud-sleuth gây ra và cách khắc phục
Gần đây, tôi muốn nhận ra rằng cấu hình của OpenFeign có thể được làm mới động trong dự án (chủ yếu là cấu hình Tùy chọn của Feign), ví dụ:
?
1
2
3
4
5
6
7
8
|
giả vờ:
khách hàng:
cấu hình:
mặc định
:
# Hết thời gian liên kết
kết nốiThời gian chờ:
500
#Đọc hết thời gian
thời gian chờ đọc:
8000
|
Chúng tôi có thể nhận thấy rằng thời gian chờ để gọi một FeignClient nhất định là không hợp lý và cần phải sửa đổi tạm thời. Chúng tôi không muốn khởi động lại quy trình hoặc làm mới toàn bộ ApplicationContext vì điều này, vì vậy chúng tôi đã đặt phần cấu hình này vào spring-cloud. -config và sử dụng tính năng làm mới động. Cơ chế được làm mới. Bản chính thức cung cấp phương thức cấu hình này, tham khảo: Tài liệu chính thức - Hỗ trợ Spring @RefreshScope.
Tức là thêm cấu hình cho dự án:
?
1
|
feign.client.refresh-enabled:
ĐÚNG VẬY
|
Tuy nhiên, trong dự án của chúng tôi, sau khi thêm cấu hình này, quá trình khởi động không thành công và đã báo cáo lỗi không thể tìm thấy các hạt liên quan:
Nguyên nhân do: org.springframework.beans.factory.NoSuchBeanDefinitionException: Không có bean nào có tên 'feign.Request.Options-testService1Client' có sẵn tại org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:863) tại org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1344) tại org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309) tại org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213) tại org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1160) tại org.springframework.cloud.openfeign.FeignContext.getInstance(FeignContext.java:57) tại org.springframework.cloud.openfeign.FeignClientFactoryBean.getOptionsByName(FeignClientFactoryBean.java:363) tại org.springframework.cloud.openfeign.FeignClientFactoryBean.configureUsingConfiguration(FeignClientFactoryBean.java:195) tại org.springframework.cloud.openfeign.FeignClientFactoryBean.configureFeign(FeignClientFactoryBean.java:158) tại org.springframework.cloud.openfeign.FeignClientFactoryBean.feign(FeignClientFactoryBean.java:132) tại org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:382) tại org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:371) tại org.springframework.cloud.openfeign.FeignClientsRegistrar.lambda$registerFeignClient$0(FeignClientsRegistrar.java:235) tại org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1231) tại org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1173) tại org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) tại org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ... 74 nữa 。
Phân tích vấn đề
Thông qua tên Bean này, chúng ta thực sự có thể thấy rằng Bean này chính là Feign.Options mà chúng tôi đề cập lần đầu tiên là được làm mới động, chứa các cấu hình như thời gian chờ kết nối và thời gian chờ đọc. Phần sau tên là contextId trong chú thích @FeignClient phía trên FeignClient mà chúng tôi đã tạo.
Khi tạo FeignClient, bạn cần tải Feign.Options Bean này. Mỗi FeignClient có ApplicationContext riêng. Bean Feign.Options này thuộc về ApplicationContext riêng biệt của mỗi FeignClient. Điều này được thực hiện thông qua NamedContextFactory của Spring Cloud. Để phân tích sâu hơn về NamedContextFactory, các bạn có thể tham khảo bài viết này của mình:
Bật làm mới động cho cấu hình OpenFeign thực sự có nghĩa là làm mới Feign.Options Bean của mỗi FeignClient. Vậy làm thế nào để đạt được nó? Trước tiên chúng ta hãy xem việc triển khai các hạt làm mới động của đám mây mùa xuân. Đầu tiên chúng ta cần hiểu Scope là gì.
Phạm vi đậu
Hiểu theo nghĩa đen thì Scope là phạm vi của Bean. Từ quan điểm triển khai, Phạm vi là cách chúng ta có được Bean khi chúng ta có được nó.
Spring framework đi kèm với hai Scope quen thuộc, đó là singleton và nguyên mẫu. Singleton có nghĩa là mỗi khi bạn nhận được Bean từ BeanFactory (getBean), cùng một đối tượng sẽ được trả về mỗi lần cho cùng một Bean, tức là chế độ singleton. Nguyên mẫu có nghĩa là mỗi khi bạn nhận được Bean từ BeanFactory, một đối tượng mới sẽ được tạo và trả về cho cùng một Bean mỗi lần, tức là ở chế độ xuất xưởng.
Đồng thời, chúng ta cũng có thể mở rộng Phạm vi theo nhu cầu của bản thân và xác định cách lấy Đậu. Để đưa ra một ví dụ đơn giản, chúng tôi tùy chỉnh TestScope. Phạm vi tùy chỉnh trước tiên cần xác định một lớp triển khai giao diện org.springframework.beans.factory.config.Scope để xác định các hoạt động liên quan đến việc lấy các hạt trong Phạm vi này.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
công cộng
giao diện
Phạm vi {
Đối tượng get(String name, ObjectFactory> objectFactory);
@Có thể rỗng
Đối tượng remove(String name);
vô hiệu
registerDestructionCallback(Tên chuỗi, Gọi lại có thể chạy);
Đối tượng resolveContextualObject(String key);
Chuỗi getConversationId();
}
|
Hãy triển khai một Phạm vi đơn giản:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
công cộng
tĩnh
lớp học
Phạm vi kiểm tra
thực hiện
Phạm vi {
@Ghi đè
công cộng
Đối tượng get(String name, ObjectFactory> objectFactory) {
trở lại
objectFactory.getObject();
}
@Ghi đè
công cộng
Đối tượng xóa (Tên chuỗi) {
trở lại
vô giá trị
;
}
@Ghi đè
công cộng
vô hiệu
registerDestructionCallback(Tên chuỗi, Gọi lại có thể chạy) {
}
@Ghi đè
công cộng
Đối tượng resolveContextualObject(String key) {
trở lại
vô giá trị
;
}
@Ghi đè
công cộng
Chuỗi getConversationId() {
trở lại
vô giá trị
;
}
}
|
Phạm vi này chỉ thực hiện phương thức get. Tạo một Bean mới trực tiếp từ objectFactory đã truyền. Mỗi lệnh gọi BeanFactory.getFactory theo loại Scope này sẽ trả về một Bean mới, và các Bean thuộc loại Scope này được tự động nạp vào các Bean khác nhau cũng là các instance khác nhau. Viết bài kiểm tra:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@Cấu hình
công cộng
tĩnh
lớp học
Cấu hình {
@Đậu
@tổ chức
.springframework.context.annotation.Scope(giá trị =
"Phạm vi thử nghiệm"
)
công cộng
Một a() {
trở lại
mới
MỘT();
}
@Autowired
riêng tư
Một là;
}
công cộng
tĩnh
lớp học
MỘT {
công cộng
vô hiệu
Bài kiểm tra() {
Hệ thống.out.println(
cái này
);
}
}
|
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
công cộng
tĩnh
vô hiệu
main(String[] args) {
Chú thíchConfigApplicationContext chú thíchConfigApplicationContext =
mới
Chú thíchConfigApplicationContext();
chú thíchConfigApplicationContext.getBeanFactory().registerScope(
"Phạm vi thử nghiệm"
,
mới
Phạm vi kiểm tra());
chú thíchConfigApplicationContext.register(Config.
lớp học
);
chú thíchConfigApplicationContext.refresh();
Cấu hình config = annotationConfigApplicationContext.getBean(Config.
lớp học
);
config.a.test();
chú thíchConfigApplicationContext.getBean(A.
lớp học
).Bài kiểm tra();
chú thíchConfigApplicationContext.getBean(A.
lớp học
).Bài kiểm tra();
}
|
Khi thực thi mã, từ đầu ra có thể thấy rằng ba chữ A này là các đối tượng khác nhau:
?
1
2
3
|
com.hopegaming.spring.cloud.parent.ScopeTest$A
@5241cf67
com.hopegaming.spring.cloud.parent.ScopeTest$A
@716a7124
com.hopegaming.spring.cloud.parent.ScopeTest$A
@77192705
|
Hãy sửa đổi Bean của chúng ta một lần nữa và biến nó thành Bean dùng một lần:
?
1
2
3
4
5
6
7
8
9
10
|
công cộng
tĩnh
lớp học
MỘT
thực hiện
Dùng một lầnĐậu {
công cộng
vô hiệu
Bài kiểm tra() {
Hệ thống.out.println(
cái này
);
}
@Ghi đè
công cộng
vô hiệu
hủy hoại()
ném
Ngoại lệ {
Hệ thống.out.println(
cái này
+
"bị phá hủy"
);
}
}
|
Sau đó sửa đổi Phạm vi tùy chỉnh của chúng tôi:
?
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
31
32
33
|
công cộng
tĩnh
lớp học
Phạm vi kiểm tra
thực hiện
Phạm vi {
riêng tư
Gọi lại có thể chạy được;
@Ghi đè
công cộng
Đối tượng get(String name, ObjectFactory> objectFactory) {
trở lại
objectFactory.getObject();
}
@Ghi đè
công cộng
Đối tượng xóa (Tên chuỗi) {
System.out.println(tên +
"đã bị xóa"
);
cái này
.callback.run();
Hệ thống.out.println(
"gọi lại đã hoàn tất"
);
trở lại
vô giá trị
;
}
@Ghi đè
công cộng
vô hiệu
registerDestructionCallback(Tên chuỗi, Gọi lại có thể chạy) {
Hệ thống.out.println(
"registerDestructionCallback được gọi"
);
cái này
.callback = gọi lại;
}
@Ghi đè
công cộng
Đối tượng resolveContextualObject(String key) {
Hệ thống.out.println(
"resolveContextualObject được gọi"
);
trở lại
vô giá trị
;
}
@Ghi đè
công cộng
Chuỗi getConversationId() {
Hệ thống.out.println(
"getConversationId được gọi"
);
trở lại
vô giá trị
;
}
}
|
Trong mã kiểm tra, hãy thêm lệnh gọi destroyScopedBean để hủy đậu:
?
1
|
chú thíchConfigApplicationContext.getBeanFactory().destroyScopedBean(
"Một"
);
|
Chạy mã và bạn có thể thấy đầu ra tương ứng:
registerDestructionCallback được gọi là a đã xóa com.hopegaming.spring.cloud.parent.ScopeTest$A@716a7124 đã hủy cuộc gọi lại hoàn tất 。
Đối với dùng một lầnBean hoặc các loại đậu khác có loại vòng đời liên quan, BeanFactory sẽ chuyển các lệnh gọi lại hoạt động cần thiết cho vòng đời thông qua registerDestructionCallback. Khi sử dụng BeanFactory.destroyScopedBean để tiêu diệt một Bean, phương thức loại bỏ của Scope sẽ được gọi. Khi thao tác hoàn tất, chúng ta có thể gọi lại để hoàn thành vòng đời của Bean.
Tiếp theo, chúng tôi cố gắng triển khai Phạm vi đơn lẻ theo cách rất đơn giản, chủ yếu dựa trên ConcurrentHashMap:
?
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
31
32
33
34
35
36
37
38
39
40
41
|
công cộng
tĩnh
lớp học
Phạm vi kiểm tra
thực hiện
Phạm vi {
riêng tư
cuối cùng
ConcurrentHashMap bản đồ =
mới
ConcurrentHashMap<>();
riêng tư
cuối cùng
ConcurrentHashMap gọi lại =
mới
ConcurrentHashMap<>();
@Ghi đè
công cộng
Đối tượng get(String name, ObjectFactory> objectFactory) {
Hệ thống.out.println(
"get được gọi là"
);
trở lại
map.compute(tên, (k, v) -> {
nếu như
(trong ==
vô giá trị
) {
v = objectFactory.getObject();
}
trở lại
TRONG;
});
}
@Ghi đè
công cộng
Đối tượng xóa (Tên chuỗi) {
cái này
.map.remove(tên);
System.out.println(tên +
"đã bị xóa"
);
cái này
.callback.get(tên).run();
Hệ thống.out.println(
"gọi lại đã hoàn tất"
);
trở lại
vô giá trị
;
}
@Ghi đè
công cộng
vô hiệu
registerDestructionCallback(Tên chuỗi, Gọi lại có thể chạy) {
Hệ thống.out.println(
"registerDestructionCallback được gọi"
);
cái này
.callback.put(tên, lệnh gọi lại);
}
@Ghi đè
công cộng
Đối tượng resolveContextualObject(String key) {
trở lại
vô giá trị
;
}
@Ghi đè
công cộng
Chuỗi getConversationId() {
trở lại
vô giá trị
;
}
}
|
Chúng tôi sử dụng hai ConcurrentHashMap để lưu trữ các Bean trong Phạm vi này và Lệnh gọi lại hủy tương ứng. Trong cách triển khai này, nó tương tự như việc triển khai mẫu đơn. Sau đó sử dụng chương trình kiểm tra sau để kiểm tra:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
|
công cộng
tĩnh
vô hiệu
main(String[] args) {
Chú thíchConfigApplicationContext chú thíchConfigApplicationContext =
mới
Chú thíchConfigApplicationContext();
chú thíchConfigApplicationContext.getBeanFactory().registerScope(
"Phạm vi thử nghiệm"
,
mới
Phạm vi kiểm tra());
chú thíchConfigApplicationContext.register(Config.
lớp học
);
chú thíchConfigApplicationContext.refresh();
Cấu hình config = annotationConfigApplicationContext.getBean(Config.
lớp học
);
config.a.test();
chú thíchConfigApplicationContext.getBean(A.
lớp học
).Bài kiểm tra();
chú thíchConfigApplicationContext.getBeanFactory().destroyScopedBean(
"Một"
);
config.a.test();
chú thíchConfigApplicationContext.getBean(A.
lớp học
).Bài kiểm tra();
}
|
Trước khi hủy Bean, chúng ta sử dụng tính năng tự động tải và BeanFactory.getBean để yêu cầu Bean A và gọi phương thức kiểm tra tương ứng. Sau đó tiêu diệt Bean. Sau đó, sử dụng tính năng tự động tải và BeanFactory.getBean để yêu cầu lấy Bean A và gọi phương thức thử nghiệm. Có thể thấy từ kết quả đầu ra rằng BeanFactory.getBean yêu cầu một hạt đậu mới, nhưng hạt đậu được tải tự động vẫn là hạt đậu bị phá hủy. Vậy làm thế nào để đạt được việc nạp đậu mới tự động, tức là nạp lại?
Điều này liên quan đến một cấu hình khác phía trên chú thích Phạm vi, nhằm chỉ định chế độ proxy:
?
1
2
3
4
5
6
7
8
9
10
|
@Mục tiêu
({Loại phần tử.TYPE, Loại phần tử.METHOD})
@Giữ chân
(Chính sách lưu giữ.RUNTIME)
@Đã ghi chép
công cộng
@giao diện
Phạm vi {
@Bí danh
(
"tên phạm vi"
)
Giá trị chuỗi()
mặc định
""
;
@Bí danh
(
"giá trị"
)
Chuỗi scopeName()
mặc định
""
;
Chế độ proxy ScopedProxyMode()
mặc định
ScopedProxyMode. MẶC ĐỊNH;
}
|
Cấu hình thứ ba, ScopedProxyMode, định cấu hình xem đối tượng Bean gốc hay đối tượng Bean proxy được lấy khi lấy Bean này (điều này cũng ảnh hưởng đến việc tải tự động):
?
1
2
3
4
5
6
7
8
9
10
|
công cộng
liệt kê
Chế độ Proxy được phạm vi {
MẶC ĐỊNH,
KHÔNG,
GIAO DIỆN,
LỚP MỤC TIÊU
}
|
Hãy kiểm tra tác dụng của việc chỉ định đối tượng thực tế của Scope Bean làm proxy. Chúng tôi sửa đổi mã kiểm tra ở trên và sử dụng proxy động CGLIB. Sửa đổi mã:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Cấu hình
công cộng
tĩnh
lớp học
Cấu hình {
@Đậu
@tổ chức
.springframework.context.annotation.Scope(giá trị =
"Phạm vi thử nghiệm"
, proxyMode = ScopedProxyMode.TARGET_CLASS
)
công cộng
Một a() {
trở lại
mới
MỘT();
}
@Autowired
riêng tư
Một là;
}
|
Viết phương thức chính của bài kiểm tra:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
công cộng
tĩnh
vô hiệu
main(String[] args) {
Chú thíchConfigApplicationContext chú thíchConfigApplicationContext =
mới
Chú thíchConfigApplicationContext();
chú thíchConfigApplicationContext.getBeanFactory().registerScope(
"Phạm vi thử nghiệm"
,
mới
Phạm vi kiểm tra());
chú thíchConfigApplicationContext.register(Config.
lớp học
);
chú thíchConfigApplicationContext.refresh();
Cấu hình config = annotationConfigApplicationContext.getBean(Config.
lớp học
);
config.a.test();
chú thíchConfigApplicationContext.getBean(A.
lớp học
).Bài kiểm tra();
System.out.println(config.a.getClass());
System.out.println(annotationConfigApplicationContext.getBean(A.
lớp học
).getClass());
chú thíchConfigApplicationContext.getBeanFactory().destroyScopedBean(ScopedProxyUtils.getTargetBeanName(
"Một"
));
config.a.test();
chú thíchConfigApplicationContext.getBean(A.
lớp học
).Bài kiểm tra();
}
|
Thực hiện chương trình, kết quả là:
get được gọi là registerDestructionCallback được gọi là com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a get được gọi là com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a class com.hopegaming.spring.cloud.parent.ScopeTest$A$$EnhancerBySpringCGLIB$$2fa625ee class com.hopegaming.spring.cloud.parent.ScopeTest$A$$EnhancerBySpringCGLIB$$2fa625ee scopedTarget.a bị xóa com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a bị hủy callback kết thúc get được gọi là registerDestructionCallback được gọi là com.hopegaming.spring.cloud.parent.ScopeTest$A@3aa3193a get được gọi là com.hopegaming.spring.cloud.parent.ScopeTest$A@3aa3193a 。
Như có thể thấy từ đầu ra:
- Mỗi khi Bean được tải tự động được gọi, phương thức get của Phạm vi tùy chỉnh sẽ được gọi để lấy lại Bean.
- Mỗi khi lấy được Bean thông qua BeanFactory, phương thức get của Phạm vi tùy chỉnh cũng sẽ được gọi để lấy lại Bean.
- Phiên bản Bean thu được là một đối tượng proxy CGLIB
- Sau khi Bean bị phá hủy, cho dù nó có được thông qua BeanFactory hay Bean được tải tự động thì đó vẫn là Bean mới.
Vậy làm thế nào Scope đạt được điều này? Hãy phân tích ngắn gọn mã nguồn.
Khái niệm cơ bản về phạm vi
Nếu một Bean không khai báo bất kỳ Scope nào thì Scope của nó sẽ được gán cho một singleton, tức là Bean mặc định là một singleton. Điều này tương ứng với BeanFactory. Trước khi đăng ký Bean, cần phải tạo định nghĩa Bean. Giá trị mặc định này sẽ được gán khi Bean được xác định, tương ứng với mã nguồn:
Tóm tắtBeanFactory 。
?
1
2
3
4
5
6
7
8
9
|
được bảo vệ
RootBeanDefinition getMergedBeanDefinition(
Chuỗi BeanName, BeanDefinition bd,
@Có thể rỗng
BeanDefinition chứaBd)
ném
Ngoại lệ BeanDefinitionStoreException {
nếu như
(!StringUtils.hasLength(mbd.getScope())) {
mbd.setScope(SCOPE_SINGLETON);
}
}
|
Trước khi khai báo Bean có Phạm vi đặc biệt, chúng ta cần xác định Phạm vi tùy chỉnh và đăng ký nó với BeanFactory. Tên Phạm vi này phải là duy nhất trên toàn cầu, vì tên này được sử dụng để phân biệt các Phạm vi khác nhau sau này. Đăng ký Phạm vi mã nguồn tương ứng:
Tóm tắtBeanFactory 。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Ghi đè
công cộng
vô hiệu
registerScope(Chuỗi scopeName, Phạm vi phạm vi) {
Khẳng định.notNull(scopeName,
"Định danh phạm vi không được để là null"
);
Assert.notNull(phạm vi,
"Phạm vi không được để là null"
);
nếu như
(SCOPE_SINGLETON.bằng(scopeName) || SCOPE_PROTOTYPE.bằng(scopeName)) {
ném
mới
Ngoại lệ bất hợp pháp
"Không thể thay thế phạm vi hiện tại 'singleton' và 'prototype'"
);
}
Phạm vi trước =
cái này
.scopes.put(tên phạm vi, phạm vi);
nếu như
(trước đó !=
vô giá trị
&& trước != phạm vi) {
nếu như
(logger.isDebugEnabled()) {
logger.debug(
"Đang thay thế phạm vi '"
+ phạm viTên +
"' từ ["
+ trước +
"] ĐẾN ["
+ phạm vi +
"]"
);
}
}
khác
{
nếu như
(logger.isTraceEnabled()) {
logger.trace(
"Đang đăng ký phạm vi '"
+ phạm viTên +
"' với việc thực hiện ["
+ phạm vi +
"]"
);
}
}
}
|
Sau khi khai báo Bean có Phạm vi đặc biệt, sẽ có logic đặc biệt khi lấy Bean. Tham khảo mã nguồn cốt lõi của việc lấy Bean thông qua BeanFactory:
Tóm tắtBeanFactory 。
?
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
31
32
33
34
35
36
37
38
39
40
41
42
|
@SuppressCảnh báo
(
"chưa được kiểm tra"
)
được bảo vệ
T doGetBean(
Tên chuỗi,
@Có thể rỗng
Lớp bắt buộc Type,
@Có thể rỗng
Đối tượng[] args,
Boolean
typeCheckOnly)
ném
Ngoại lệ đậu {
nếu như
( mbd . isSingleton ( ) ) {
}
khác
nếu như
(mbd.isPrototype()) {
}
khác
{
Chuỗi scopeName = mbd.getScope();
nếu như
(!StringUtils.hasLength(scopeName)) {
ném
mới
Ngoại lệ bất hợp pháp
"Không có tên phạm vi nào được xác định cho bean ´"
+ tên phụ nữ +
"'"
);
}
Phạm vi phạm vi =
cái này
.scopes.get(tên phạm vi);
nếu như
(phạm vi ==
vô giá trị
) {
ném
mới
Ngoại lệ bất hợp pháp
"Không có phạm vi nào được đăng ký cho tên phạm vi '"
+ phạm viTên +
"'"
);
}
thử
{
Đối tượng scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(tên hạt đậu);
thử
{
trở lại
createBean(beanName, mbd, args);
}
Cuối cùng
{
sau khi tạo Prototype(tên hạt đậu);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, tên, beanName, mbd);
}
nắm lấy
(IllegalStateException ví dụ) {
ném
mới
ScopeNotActiveException(tên_bean, tên_scope, ví dụ);
}
}
}
|
Đồng thời, nếu chúng ta định nghĩa phương thức proxy của Scope Bean là CGLIB thì khi lấy định nghĩa Bean, định nghĩa Bean của Scope proxy sẽ được tạo dựa trên định nghĩa Bean ban đầu, tương ứng với mã nguồn:
Sử dụng ScopedProxyUtils.
?
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
31
32
33
34
35
36
37
|
công cộng
tĩnh
BeanDefinitionHolder createScopedProxy(Định nghĩa BeanDefinitionHolder,
Đăng ký BeanDefinitionRegistry,
Boolean
proxyTargetClass) {
Chuỗi originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
Chuỗi targetBeanName = getTargetBeanName(originalBeanName);
RootBeanDefinition proxyDefinition =
mới
RootBeanDefinition(ScopedProxyFactoryBean.
lớp học
);
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
nếu như
(mục tiêuĐịnh nghĩa
trường hợp của
AbstractBeanĐịnh nghĩa) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
targetDefinition.setAutowireỨng viên(
SAI
);
targetDefinition.setPrimary(
SAI
);
registry.registerBeanDefinition(targetBeanName, targetDefinition);
trở lại
mới
BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
riêng tư
tĩnh
cuối cùng
Chuỗi TARGET_NAME_PREFIX =
"Mục tiêu có phạm vi."
;
công cộng
tĩnh
Chuỗi getTargetBeanName(Chuỗi originalBeanName) {
trở lại
TARGET_NAME_PREFIX + tên_Bean_ban đầu;
}
|
Đậu proxy này làm gì? Trên thực tế, công dụng chính là mỗi khi bất kỳ phương thức nào của Bean được gọi, Bean sẽ được lấy thông qua BeanFactory và được gọi. Mã nguồn tham khảo:
Lớp proxy ScopedProxyFactoryBean.
?
1
2
3
4
5
6
|
công cộng
lớp học
Phạm viProxyFactoryBean
mở rộng
Cấu hình proxy
thực hiện
FactoryBean<đối tượng="">, BeanFactoryAware, AopInfrastructureBean {đối>
riêng tư
cuối cùng
SimpleBeanTargetSource có phạm viTargetSource =
mới
Nguồn mục tiêu đơn giản();
riêng tư
Đối tượng proxy;
}
|
SimpleBeanTargetSource là nguồn proxy thực tế. Phương pháp cốt lõi là sử dụng tên Bean để lấy Bean thông qua BeanFactory:
?
1
2
3
4
5
6
|
công cộng
lớp học
SimpleBeanMục tiêuNguồn
mở rộng
Tóm tắtBeanFactoryBasedTargetSource {
@Ghi đè
công cộng
Đối tượng getTarget()
ném
Ngoại lệ {
trở lại
getBeanFactory().getBean(getTargetBeanName());
}
}
|
Lấy Bean này thông qua BeanFactory Từ phân tích mã nguồn ở trên, bạn có thể biết rằng đối với Bean của Phạm vi tùy chỉnh, phương thức get của Phạm vi tùy chỉnh sẽ được gọi.
Sau đó là sự hủy diệt của Bean. Khi BeanFactory tạo đối tượng Bean, registerDestructionCallback của Phạm vi tùy chỉnh sẽ được gọi để chuyển vào lệnh gọi lại quá trình hủy Bean:
Tóm tắtBeanFactory 。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
được bảo vệ
vô hiệu
registerDisposableBeanIfNecessary(Chuỗi beanName, Đối tượng bean, RootBeanDefinition mbd) {
AccessControlContext acc = (System.getSecurityManager() !=
vô giá trị
? getAccessControlContext() :
vô giá trị
);
nếu như
(!mbd.isPrototype() && yêu cầuDestruction(bean, mbd)) {
nếu như
( mbd . isSingleton ( ) ) {
registerDisposableBean(tênbean,
mới
Bộ chuyển đổi đậu dùng một lần
bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
}
khác
{
Phạm vi phạm vi =
cái này
.scopes.get(mbd.getScope());
nếu như
(phạm vi ==
vô giá trị
) {
ném
mới
Ngoại lệ bất hợp pháp
"Không có phạm vi nào được đăng ký cho tên phạm vi '"
+ mbd.getScope() +
"'"
);
}
phạm vi.registerDestructionCallback(beanName,
mới
Bộ chuyển đổi đậu dùng một lần
bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
}
}
}
|
Khi chúng ta muốn hủy Scope Bean, chúng ta cần gọi phương thức destroyScopedBean của BeanFactory. Phương thức này sẽ gọi loại bỏ Scope tùy chỉnh:
Tóm tắtBeanFactory 。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Ghi đè
công cộng
vô hiệu
destroyScopedBean(Chuỗi tên bean) {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
nếu như
(mbd.isSingleton() || mbd.isPrototype()) {
ném
mới
Ngoại lệ bất hợp pháp
"Tên đậu '"
+ tên phụ nữ +
"' không tương ứng với một đối tượng trong phạm vi có thể thay đổi"
);
}
Chuỗi scopeName = mbd.getScope();
Phạm vi phạm vi =
cái này
.scopes.get(tên phạm vi);
nếu như
(phạm vi ==
vô giá trị
) {
ném
mới
Ngoại lệ bất hợp pháp
"Không có phạm vi SPI nào được đăng ký cho tên phạm vi '"
+ phạm viTên +
"'"
);
}
Đối tượng bean = scope.remove(beanName);
nếu như
(đậu !=
vô giá trị
) {
destroyBean(beanName, Bean, mbd);
}
}
|
Đến đây là kết thúc bài viết về việc giải quyết vấn đề dự án có thể làm mới không thể khởi động khi cấu hình Spring-Cloud-OpenFeign được bật. Để biết thêm nội dung cấu hình Spring-Cloud-OpenFeign có liên quan, vui lòng tìm kiếm các bài viết trước của tôi hoặc tiếp tục duyệt qua các chủ đề liên quan bên dưới. Bài viết mong mọi người ủng hộ mình trong thời gian tới! .
Liên kết gốc: https://www.cnblogs.com/zhxdick/p/15359268.html.
Cuối cùng, bài viết về giải quyết vấn đề kích hoạt cấu hình Spring-Cloud-OpenFeign để làm mới dự án không thể bắt đầu ở đây. Nếu bạn muốn biết thêm về cách giải quyết vấn đề kích hoạt cấu hình Spring-Cloud-OpenFeign để làm mới dự án. không thể bắt đầu. Về nội dung, vui lòng tìm kiếm các bài viết của CFSDN hoặc tiếp tục duyệt các bài viết liên quan. Tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!