Backend Development 12 min read

Master Spring Bean Injection: Prototype in Singleton, Lazy, Transactions & More

This guide explains how to handle various Spring bean injection and transaction challenges, including injecting prototype beans into singletons, using @Lazy, registering abstract beans, resolving multiple bean conflicts, forcing rollback without exceptions, injecting static fields, ensuring transaction consistency, enabling non‑public transactional methods, custom component scanning, and adding interfaces via AOP.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring Bean Injection: Prototype in Singleton, Lazy, Transactions & More

Question 1: How to correctly inject a prototype bean into a singleton bean?

Spring injects a bean only once at container startup; to obtain a new instance each time you can use one of the following approaches.

Method 1: Use a scoped proxy

<code>// Define prototype bean with proxyMode = TARGET_CLASS
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
static class HttpSecurity {}

static class SecurityFilterChain {
    @Resource
    private HttpSecurity httpSecurity;
    @Override
    public String toString() {
        return "SecurityFilterChain [httpSecurity=" + httpSecurity + "]";
    }
}
</code>

Method 2: Inject lazily

<code>static class SecurityFilterChain {
    @Resource
    // @Lazy makes the bean a proxy that is created only when first used
    @Lazy
    private HttpSecurity httpSecurity;
    @Override
    public String toString() {
        return "SecurityFilterChain [httpSecurity=" + httpSecurity + "]";
    }
}
</code>

Method 3: Retrieve from ApplicationContext each use

<code>static class SecurityFilterChain {
    @Resource
    private ApplicationContext context;
    @Override
    public String toString() {
        return "SecurityFilterChain [httpSecurity=" + httpSecurity + "]";
    }
    public void test() {
        // Get a fresh instance on each call
        HttpSecurity httpSecurity = context.getBean(HttpSecurity.class);
    }
}
</code>

Method 4: Use @Lookup (proxy‑based, omitted for brevity)

See the section “Question 3”.

Question 2: What happens when a bean class is annotated with @Lazy?

If a class is marked with @Lazy, Spring does not instantiate it during container startup; the bean is created only when it is first requested, after which it is placed in the singleton pool.

<code>public void refresh() {
    finishBeanFactoryInitialization(beanFactory);
}

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    beanFactory.preInstantiateSingletons();
}

public void preInstantiateSingletons() {
    for (String beanName : beanNames) {
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        // Skip beans that are lazy‑initialized
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            getBean(beanName);
        }
    }
}
</code>

Question 3: Can an abstract class be registered as a bean?

Yes, if the abstract class contains a method annotated with @Lookup or if you manually set a method on its BeanDefinition.

<code>@Component
static abstract class SecurityManager {
    public void execute() {
        HttpSecurity httpSecurity = httpSecurity();
        System.out.println(httpSecurity);
    }
    @Lookup
    protected abstract HttpSecurity httpSecurity();
}
</code>

Manual registration example:

<code>static abstract class SecurityProvider {
    public void authority() {}
    protected void httpSecurity() {}
}

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(SecurityProvider.class, bd -> {
    AbstractBeanDefinition definition = (AbstractBeanDefinition) bd;
    Method method = SecurityProvider.class.getDeclaredMethod("httpSecurity");
    LookupOverride override = new LookupOverride(method, "");
    definition.getMethodOverrides().addOverride(override);
});
</code>

Question 4: How to inject multiple beans of the same type?

When several beans implement the same interface, Spring throws NoUniqueBeanDefinitionException . Resolve it by:

Specifying the bean name with @Resource(name="a") .

Using @Qualifier on the desired bean and the injection point.

Assigning @Priority (lower value = higher priority).

Marking one bean with @Primary .

<code>// By name
@Component static class A implements DAO {}
@Component static class B implements DAO {}
@Component static class Service {
    @Resource(name = "a")
    private DAO d;
}

// By qualifier
@Component static class A implements DAO {}
@Component @Qualifier static class B implements DAO {}
@Component static class Service {
    @Resource
    @Qualifier
    private DAO d;
}

// By priority
@Component @Priority(2) static class A implements DAO {}
@Component @Priority(1) static class B implements DAO {}
@Component static class Service {
    @Resource
    private DAO d; // B is injected because it has higher priority
}

// By primary
@Component @Primary static class A implements DAO {}
</code>

Question 5: How to trigger a transaction rollback without throwing an exception?

<code>@Transactional
public void save() {
    Person person = new Person();
    person.setAge(36);
    person.setName("张三");
    int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(), person.getName());
    System.out.printf("保存Person 结果:%d%n", result);
    // Mark the transaction for rollback
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
</code>

Question 6: Can static fields be injected?

By default Spring does not support injection into static fields, but you can work around it.

Method 1: Manual injection after context startup

<code>private static PersonDAO dao;
@Resource private ApplicationContext context;

@PostConstruct
public void init() {
    dao = context.getBean(PersonDAO.class);
}
</code>

Method 2: Modify Spring source to allow static @Resource

<code>public class CommonAnnotationBeanPostProcessor {
    private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
        if (field.isAnnotationPresent(Resource.class)) {
            // Comment out the static‑field check in the source code
            /* if (Modifier.isStatic(field.getModifiers())) {
                throw new IllegalStateException("@Resource annotation is not supported on static fields");
            } */
        }
    }
}
</code>

Question 7: How to ensure transaction consistency in multithreaded scenarios?

Refer to the detailed article “Spring multithreading transaction consistency”. (The link and illustrative image have been omitted as they are not core content.)

Question 8: Does Spring only support @Transactional on public methods?

By default yes, but you can override this by providing a custom TransactionAttributeSource bean.

<code>@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
    // Setting false allows protected and package‑private methods to be proxied
    return new AnnotationTransactionAttributeSource(false);
}
</code>

Make sure the allowBeanDefinitionOverriding property of the BeanFactory is set to true .

Question 9: How to make classes annotated with a custom annotation managed by Spring?

Method 1: Custom @ComponentScan include filter

<code>@ComponentScan(useDefaultFilters = false, includeFilters = {
    @Filter(type = FilterType.ANNOTATION, classes = {PackComponent.class})
})
static class AppConfig {}
</code>

Method 2: Custom type filter

<code>@ComponentScan(useDefaultFilters = false, includeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = {PackTypeFilter.class})
})
static class AppConfig {}

static class PackTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return metadataReader.getAnnotationMetadata().hasAnnotation(PackCount.class.getName());
    }
}
</code>

Question 10: How to give a class an additional interface without modifying its source?

Use an introduction interceptor (AOP) to add the interface at runtime.

<code>static interface CommonDAO { void play(); }

static class CustomIntroductionInterceptor implements IntroductionInterceptor, CommonDAO {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (this.implementsInterface(invocation.getMethod().getDeclaringClass())) {
            return invocation.getMethod().invoke(this, invocation.getArguments());
        }
        return invocation.proceed();
    }
    @Override
    public boolean implementsInterface(Class<?> intf) { return intf.isAssignableFrom(this.getClass()); }
    @Override
    public void play() { System.out.println("通用方法..."); }
}

@Component
static class CustomAopCreator extends AbstractAutoProxyCreator {
    @Override
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) {
        return new Object[] { new DefaultIntroductionAdvisor(new CustomIntroductionInterceptor(), CommonDAO.class) };
    }
}

// Test
try (GenericApplicationContext context = new GenericApplicationContext()) {
    context.registerBean(CustomAopCreator.class);
    context.registerBean("us", UserService.class);
    context.refresh();
    Object us = context.getBean("us");
    System.out.println(Arrays.toString(us.getClass().getInterfaces()));
    if (us instanceof CommonDAO) {
        ((CommonDAO) us).play();
    }
    ((UserService) us).save();
}
</code>

All questions are answered with code snippets and explanations.

JavaSpringdependency injectionTransaction ManagementBean Scope
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.