有多少人用過Spring的@Lookup注解?
環(huán)境:Spring5.3.23
1. 簡介
Lookup方法注入能夠根據@Lookup注解的value屬性值或被注解該方法的返回值,從容器中查找bean作為方法的返回值對象使用。Spring容器會通過CGLIB生成當前類的代理,然后重寫被@Lookup注解的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lookup {
/**
* 根據你設置的value值,從容器中查找對應的bean,如果沒有指定value值
* 那么會根據被注解方法的返回值類型從容器中查找相應的bean
*/
String value() default "";
}
2. 應用案例
準備基礎類
static interface HttpSecurity {
void http() ;
}
static class HtmlHttpSecurity implements HttpSecurity {
@Override
public void http() {
System.out.println("Html HttpSecurity...") ;
}
}
定義一個抽象類
該類中有一個抽象方法被@Lookup注解標注
static abstract class SecurityManager {
public void execute() {
HttpSecurity httpSecurity = httpSecurity() ;
System.out.println(httpSecurity.getClass()) ;
httpSecurity.http() ;
}
@Lookup("html")
protected abstract HttpSecurity httpSecurity() ;
}
注冊Bean
將上面的類注冊到容器中
public static void main(String[] args) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.registerBean("html", HtmlHttpSecurity.class) ;
context.register(SecurityManager.class) ;
context.refresh() ;
SecurityManager sm = context.getBean(SecurityManager.class);
sm.execute() ;
System.out.println(sm.getClass()) ;
}
}
執(zhí)行結果
class com.pack.main.lookup.MethodLookupInjectMain2$HtmlHttpSecurity
Html HttpSecurity...
class com.pack.main.lookup.MethodLookupInjectMain2$SecurityManager$$EnhancerBySpringCGLIB$$ae697832
SecurityManager通過CGLIB被創(chuàng)建為了代理類。同時execute方法中HttpSecurity對象就是HtmlHttpSecurity類,也就是容器通過查找注入的。
去掉@Lookup注解的value屬性
@Lookup
protected abstract HttpSecurity httpSecurity() ;
繼續(xù)執(zhí)行上面的測試代碼,程序也能正常的輸出。
在添加一個HttpSecurity的實現
static class CssHttpSecurity implements HttpSecurity {
@Override
public void http() {
System.out.println("Css HttpSecurity...") ;
}
}
將上面的類也注冊到容器中,如果這時候你的@Lookup注解沒有value屬性將會報錯
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.pack.main.lookup.MethodLookupInjectMain2$HttpSecurity' available: expected single matching bean but found 2: html,css
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1273)
expected single matching bean but found 2: html,css;錯誤提示你有2個bean,期望的是一個,所以這里我們必須添加value屬性,指明要查找的bean對象。
繼續(xù)測試,被@Lookup注解的方法是不是必須是抽象的?
修改代碼如下:
@Lookup("html")
protected HttpSecurity httpSecurity() {
return null ;
};
方法返回null,執(zhí)行結果
class com.pack.main.lookup.MethodLookupInjectMain2$HtmlHttpSecurity
Html HttpSecurity...
class com.pack.main.lookup.MethodLookupInjectMain2$SecurityManager$$EnhancerBySpringCGLIB$$ae697832
程序正常,沒有問題。這也說明了,這里和具體的返回值是沒有關系的。
3. 實現原理
容器在創(chuàng)建一個Bean對象時會執(zhí)行如下步驟
public abstract class AbstractAutowireCapableBeanFactory {
protected Object doCreateBean() {
BeanWrapper instanceWrapper = null;
// ...
if (instanceWrapper == null) {
// 創(chuàng)建實例
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// ...
}
protected BeanWrapper createBeanInstance() {
// 查找構造方法
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 實例化bean對象;上面一步對@Lookup注解的方法進行了初始化查找
return instantiateBean(beanName, mbd);
}
protected Constructor<?>[] determineConstructorsFromBeanPostProcessors(@Nullable Class<?> beanClass, String beanName)
throws BeansException {
if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) {
// 調用BeanPostProcessor
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
// 這里通過了AutowiredAnnotationBeanPostProcessor處理器
// 這里其實最終返回了null,關鍵就是執(zhí)行了下面的動作,查找了@Lookup注解的方法進行解析
// 保存到BeanDefinition中
Constructor<?>[] ctors = bp.determineCandidateConstructors(beanClass, beanName);
if (ctors != null) {
return ctors;
}
}
}
return null;
}
}
AutowiredAnnotationBeanPostProcessor處理器
public class AutowiredAnnotationBeanPostProcessor {
public Constructor<?>[] determineCandidateConstructors() {
do {
// 遍歷當前類的所有方法
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
// 獲取方法是的@Lookup注解
Lookup lookup = method.getAnnotation(Lookup.class);
if (lookup != null) {
// 如果存在則構造LookupOverride對象,將當前的Method及注解的value值傳入
LookupOverride override = new LookupOverride(method, lookup.value());
try {
RootBeanDefinition mbd = (RootBeanDefinition)this.beanFactory.getMergedBeanDefinition(beanName);
// 最后將其保存到BeanDefinition中
mbd.getMethodOverrides().addOverride(override);
}
}
});
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
}
}
繼續(xù)上面執(zhí)行到instantiateBean方法
public abstract class AbstractAutowireCapableBeanFactory {
protected BeanWrapper instantiateBean() {
// getInstancetiationStrategy方法返回了CglibSubclassingInstantiationStrategy
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
}
}
進入SimpleInstantiationStrategy#instanceiate方法
public class SimpleInstantiationStrategy implements InstantiationStrategy {
public Object instantiate(RootBeanDefinition bd) {
// 判斷當前的BeanDefinition中是否有LookupOverride
// 上面查找到后已經將其保存到了BeanDefinition中。
if (!bd.hasMethodOverrides()) {
// ...
}
else {
// 存在則通過CGLIB進行創(chuàng)建代理
// 進入CglibSubclassingInstantiationStrategy
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
}
CglibSubclassingInstantiationStrategy
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
return instantiateWithMethodInjection(bd, beanName, owner, null);
}
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Constructor<?> ctor, Object... args)
return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
}
}
CglibSubclassCreator
private static class CglibSubclassCreator {
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
{NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};
public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
// 創(chuàng)建代理,當前類的子類
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
// ...
}
private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(beanDefinition.getBeanClass());
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
if (this.owner instanceof ConfigurableBeanFactory) {
ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader();
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));
}
enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition));
// 我們主要關注這里,這些都是CGLIB相關的知識了。
// 關注LookupOverrideMethodInterceptor攔截器
enhancer.setCallbackTypes(CALLBACK_TYPES);
return enhancer.createClass();
}
}
LookupOverrideMethodInterceptor攔截器
private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all
// 判斷是否設置了@Lookup value值
if (StringUtils.hasText(lo.getBeanName())) {
// 根據value指定的值查找bean對象
Object bean = (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) :
this.owner.getBean(lo.getBeanName()));
return (bean.equals(null) ? null : bean);
}
// 如果沒有設置value,那么會根據方法的返回值類型在容器中查找bean
else {
ResolvableType genericReturnType = ResolvableType.forMethodReturnType(method);
return (argsToUse != null ? this.owner.getBeanProvider(genericReturnType).getObject(argsToUse) :
this.owner.getBeanProvider(genericReturnType).getObject());
}
}
}
以上就是@Lookup注解的原理
總結:Spring的@Lookup注解提供了一種靈活的機制,用于在運行時動態(tài)地創(chuàng)建和初始化beans。通過@Lookup,開發(fā)者可以在配置中指定一個方法,該方法會在運行時被調用以獲取相應的bean實例。這使得在某些特定條件下或在運行時配置變更時,能夠動態(tài)地選擇和創(chuàng)建不同的bean實現。
完畢!!!