Backend Development 10 min read

Mastering Spring @Lookup: Dynamic Bean Injection Explained with Code

This article explains Spring's @Lookup method injection, shows how to define the annotation, register beans, run examples, and dives into the underlying CGLIB proxy mechanism and bean creation process for dynamic bean lookup.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring @Lookup: Dynamic Bean Injection Explained with Code

1. Introduction

Lookup method injection allows a method annotated with @Lookup to obtain a bean from the Spring container based on the annotation's value attribute or the method's return type. Spring creates a CGLIB proxy for the class and overrides the annotated method.

<code>@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lookup {
    /**
     * If a value is set, the container looks up the bean with that name.
     * If no value is set, the return type of the method is used to find a matching bean.
     */
    String value() default "";
}</code>

2. Usage Example

Prepare base interface

<code>static interface HttpSecurity {
    void http();
}

static class HtmlHttpSecurity implements HttpSecurity {
    @Override
    public void http() {
        System.out.println("Html HttpSecurity...");
    }
}</code>

Define an abstract class

The class contains an abstract method annotated with @Lookup.

<code>static abstract class SecurityManager {
    public void execute() {
        HttpSecurity httpSecurity = httpSecurity();
        System.out.println(httpSecurity.getClass());
        httpSecurity.http();
    }

    @Lookup("html")
    protected abstract HttpSecurity httpSecurity();
}</code>

Register beans

<code>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());
    }
}</code>

Execution result

<code>class com.pack.main.lookup.MethodLookupInjectMain2$HtmlHttpSecurity
Html HttpSecurity...
class com.pack.main.lookup.MethodLookupInjectMain2$SecurityManager$$EnhancerBySpringCGLIB$$ae697832</code>

The SecurityManager is proxied by CGLIB, and the httpSecurity object returned in execute() is an instance of HtmlHttpSecurity , injected via the @Lookup mechanism.

If the @Lookup annotation does not specify a value and multiple beans of the required type exist, Spring throws NoUniqueBeanDefinitionException . Adding a value resolves the ambiguity.

Is the @Lookup method required to be abstract?

Changing the method to a concrete one that returns null still works, showing that the method does not need to be abstract; Spring replaces the implementation at runtime.

3. Implementation Details

When Spring creates a bean, it follows these steps:

<code>public abstract class AbstractAutowireCapableBeanFactory {
    protected Object doCreateBean() {
        BeanWrapper instanceWrapper = null;
        // ...
        if (instanceWrapper == null) {
            // create instance
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        // ...
    }

    protected BeanWrapper createBeanInstance() {
        // find constructors
        Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        // instantiate bean; @Lookup methods are processed here
        return instantiateBean(beanName, mbd);
    }

    protected Constructor<?>[] determineConstructorsFromBeanPostProcessors(@Nullable Class<?> beanClass, String beanName) {
        if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) {
            for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
                Constructor<?>[] ctors = bp.determineCandidateConstructors(beanClass, beanName);
                if (ctors != null) {
                    return ctors;
                }
            }
        }
        return null;
    }
}</code>

The AutowiredAnnotationBeanPostProcessor scans methods for @Lookup, creates a LookupOverride , and stores it in the bean definition.

<code>public class AutowiredAnnotationBeanPostProcessor {
    public Constructor<?>[] determineCandidateConstructors() {
        do {
            ReflectionUtils.doWithLocalMethods(targetClass, method -> {
                Lookup lookup = method.getAnnotation(Lookup.class);
                if (lookup != null) {
                    LookupOverride override = new LookupOverride(method, lookup.value());
                    RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName);
                    mbd.getMethodOverrides().addOverride(override);
                }
            });
            targetClass = targetClass.getSuperclass();
        } while (targetClass != null && targetClass != Object.class);
    }
}</code>

During bean instantiation, SimpleInstantiationStrategy checks for method overrides. If present, it delegates to CglibSubclassingInstantiationStrategy which creates a CGLIB subclass.

<code>public class SimpleInstantiationStrategy implements InstantiationStrategy {
    public Object instantiate(RootBeanDefinition bd) {
        if (!bd.hasMethodOverrides()) {
            // normal instantiation
        } else {
            // create proxy via CGLIB
            return instantiateWithMethodInjection(bd, beanName, owner);
        }
    }
}

public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {
    protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
        return new CglibSubclassCreator(bd, owner).instantiate(null);
    }
}

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) {
        Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
        // ... instantiate subclass
    }
    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));
        enhancer.setCallbackTypes(CALLBACK_TYPES);
        return enhancer.createClass();
    }
}</code>

The LookupOverrideMethodInterceptor intercepts calls to @Lookup methods. If a value is set, it retrieves the bean by name; otherwise it resolves the bean by the method's return type.

<code>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 (StringUtils.hasText(lo.getBeanName())) {
            Object bean = (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) : this.owner.getBean(lo.getBeanName()));
            return bean.equals(null) ? null : bean;
        } else {
            ResolvableType genericReturnType = ResolvableType.forMethodReturnType(method);
            return argsToUse != null ? this.owner.getBeanProvider(genericReturnType).getObject(argsToUse) : this.owner.getBeanProvider(genericReturnType).getObject();
        }
    }
}</code>

In summary, Spring's @Lookup annotation provides a flexible way to obtain beans dynamically at runtime. By declaring a method with @Lookup, developers can let the container supply the appropriate bean instance based on a name or return type, enabling dynamic selection and creation of bean implementations.

Done!!

JavaSpringdependency injectionCglibLookupmethod injection
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.