环境:Spring5.3.23
问题一、单例Bean中如何正确注入多例Bean?
首先,你的知道Spring容器在启动后对你所依赖的Bean只会注入一次,后续在使用过程中都是这一个实例。现在希望的是每次在使用所依赖的这个Bean都是一个新对象。
答:可以通过如下3中方式。
方式1:
// 在定义原型Bean时设置proxyMode属性为TARGET_CLASS// 这样容器在启动时就会将当前HttpSecurity注册为代理Bean(ScopedProxyFactoryBean)(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)static class HttpSecurity {}static class SecurityFilterChain {private HttpSecurity httpSecurity ;public String toString() {return "SecurityFilterChain [httpSecurity=" + httpSecurity + "]";}}
方式2:
static class SecurityFilterChain {// 在注入的时候使用@Lazy注解,这样对应的BeanPostProcessor就会创建一个代理ji你先注入private HttpSecurity httpSecurity ;public String toString() {return "SecurityFilterChain [httpSecurity=" + httpSecurity + "]";}}
方式3:
static class SecurityFilterChain {private ApplicationContext context ;public void test() {// 在每次使用的时候动态获取HttpSecurity httpSecurity = context.getBean(HttpSecurity.class) ;}public String toString() {return "SecurityFilterChain [httpSecurity=" + httpSecurity + "]";}}
方式4:
通过@Lookup,该种方式最终还是通过代理方式实现,且该注解用的非常少,这里就不做介绍了。请参考《问题三》
问题二、使用@Lazy标注一个Bean类,会发生什么?
在上面上一题中已经使用@Lazy解决了多例Bean注入的问题。但如果用在一个@Component注解的类上呢?相信应该基本不会这么玩吧?
答:如果一个类被@Lazy那么容器在启动过程中会判断当前类的注解信息中是否有@Lazy注解,如果有那么就不会实例化,只有你真正使用的时候才会去实例化,然后将该类注册到单例池中。
源码:
public void refresh() {finishBeanFactoryInitialization(beanFactory);}protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {beanFactory.preInstantiateSingletons();}public void preInstantiateSingletons() {for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);// 这里就判断了你当前的BeanDefinition是否被设置了延迟初始化也就是类上使用l饿@Lazy注解if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {getBean(...) ;}}}
问题三、抽象类能不能被注册为Bean?
答:可以。但是前提是你当前的抽象类中某个方法的有@Lookup注解标注,或者你手动注册Bean的时候给BeanDefinition对象随意指定一个方法即可。示例如下:
@Componentstatic abstract class SecurityManager {public void execute() {HttpSecurity httpSecurity = httpSecurity() ;System.out.println(httpSecurity) ;}// 只有当前抽象类中有了这个注解就可以本注册为Bean对象// 同时这里还解决了注入多例bean 的问题@Lookupprotected abstract HttpSecurity httpSecurity() ;}
手动通过BeanDefinition注册Bean对象设置任意的方法
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 = null ;try {// 随意指定一个方法method = SecurityProvider.class.getDeclaredMethod("httpSecurity") ;LookupOverride override = new LookupOverride(method, "") ;// 给当前的BeanDefinition对象设置MethodOverrides属性值即可definition.getMethodOverrides().addOverride(override) ;} catch (Exception e) {e.printStackTrace();}}) ;
问题四、多个相同类型的Bean如何正确的被注入(如:多个类实现了同一个接口)?
当在容器有多个相同类型的Bean时,启动容器并不会报错,如果在这种情况下我们注入父类对象时,容器就会报如下错:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.pack.main.multi_same_bean_priority.SameBeanMain$DAO' available: expected single matching bean but found 2: sameBeanMain.A,sameBeanMain.Bat org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)
出现这样的错误,我们可以通过如下方式来解决:
方式1:指定要注入的Bean名称
static class A implements DAO {}static class B implements DAO {}static class Service {(name = "a")private DAO d ;}
方式2:通过限定符
static class A implements DAO {}static class B implements DAO {}static class Service {// 指定了注入的bean也必须被@Qualifier标注// 其实我们也可以自定义注解,但是需要我们注册相应的注解// 通过QualifierAnnotationAutowireCandidateResolverprivate DAO d ;}
方式3:通过优先级注解@Priority
(2)static class A implements DAO {}(1)static class B implements DAO {}static class Service {// 上面优先级高的会被注入,值越小优先级越高private DAO d ;}
方式4:通过@Primary注解
static class A implements DAO {}
问题五、不抛出异常的情况下如何让事务回滚?
如果你看过源码应该能答出来,其实我们只需要在事务的方法中将设置状态设置为只读即可。代码如下:
@Transactionalpublic 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);// 标记回滚// 这里就不会抛出异常了TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() ;}
问题六、静态字段到底能不能被注入?
答:默认不行,但是我们可以通过如下方式进行注入。
方式1:
private static PersonDAO dao ;private ApplicationContext context ;public void init() {dao = context.getBean(PersonDAO.class) ;}
方式2:
该种方式需要修改源码:
public class CommonAnnotationBeanPostProcessor {private InjectionMetadata buildResourceMetadata(Class<?> clazz) {if (field.isAnnotationPresent(Resource.class)) {// 在源码中将这段代码注释即可/* if (Modifier.isStatic(field.getModifiers())) {throw new IllegalStateException("@Resource annotation is not supported on static fields");} */}}}
问题七、多线程下如何保证事务的一致性?
答:请参考:《详解Spring多线程下如何保证事务的一致性》,这篇文章详细讲了多线程下如何保证事务的一致性(其实还有其它简单的实现
)。
问题八、Spring事物是不是只支持public修饰的方法?
答:默认情况下是。但可以通过如下方式进行覆盖默认的Bean。
我们只需要提供如下的一个bean定义即可解决默认只支持public方法事务
(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionAttributeSource transactionAttributeSource() {// 设置为false,受保护的及package-private也可以被代理return new AnnotationTransactionAttributeSource(false) ;}
这种方式前提是你的允许BeanFactory的allowBeanDefinitionOverriding属性设置为true。
问题九、如何让自定义注解标注的类也能被Spring容器所管理?
答:可以通过如下方式。
方式1:
自定义@ComponentScan注解
(useDefaultFilters = false, includeFilters = {(type = FilterType.ANNOTATION, classes = {PackComponent.class})})static class AppConfig {}
这样spring容器在扫描classpath下的所有类文件的时候就会判断当前的类上是否有@PackComponent注解,如果有则会被注册为bean。
方式2:
自定义类型过滤
@ComponentScan(useDefaultFilters = false, includeFilters = {@Filter(type = FilterType.CUSTOM, classes = {PackTypeFilter.class})})static class AppConfig {}
PackTypeFilter
static class PackTypeFilter implements TypeFilter {public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {System.out.println(metadataReader.getAnnotationMetadata()) ;return metadataReader.getAnnotationMetadata().hasAnnotation(PackCount.class.getName()) ;}}
问题十、如何在不修改源码的情况下让类具备指定接口的功能?
意思是:现在有个UserService类,我希望在不修改代码的情况下让UserService具备CommonDAO(接口)的能力。一般都是去实现这个接口,现在呢不允许修改源码
答:通过引介通知,直接看代码。
static interface CommonDAO {void play() ;}// 我们在引介拦截器上实现我们的目标需求static class CustomIntroductionInterceptor implements IntroductionInterceptor, CommonDAO {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {if (this.implementsInterface(invocation.getMethod().getDeclaringClass())) {return invocation.getMethod().invoke(this, invocation.getArguments()) ;}return invocation.proceed() ;}@Overridepublic boolean implementsInterface(Class<?> intf) {return intf.isAssignableFrom(this.getClass()) ;}@Overridepublic void play() {System.out.println("通用方法...") ;}}// 自定义BeanPostProcessor,就是创建代理对象的核心@Componentstatic class CustomAopCreator extends AbstractAutoProxyCreator {@Overrideprotected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,TargetSource customTargetSource) throws BeansException {return new Object[] {new DefaultIntroductionAdvisor(new CustomIntroductionInterceptor(), CommonDAO.class)} ;}@Overrideprotected boolean shouldSkip(Class<?> beanClass, String beanName) {return super.shouldSkip(beanClass, beanName) ;}}
测试
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 dao = (CommonDAO) us ;dao.play() ;}UserService user = (UserService) us ;user.save() ;}
这样就实现了不修改源码的情况下UserService具备了CommonDAO接口的能力。
你都答对了几个?欢迎交流讨论
求关注+转发



