大数跨境
0
0

【难!难!难!】Spring夺命10连问,你能答上几个?有代码!

【难!难!难!】Spring夺命10连问,你能答上几个?有代码! Spring全家桶实战案例
2023-10-15
1
导读:这才是真正的Spring夺命10连问,你能答上几个?能全答对的绝对是高手!

环境:Spring5.3.23


问题一、单例Bean中如何正确注入多例Bean?

首先,你的知道Spring容器在启动后对你所依赖的Bean只会注入一次,后续在使用过程中都是这一个实例。现在希望的是每次在使用所依赖的这个Bean都是一个新对象。

答:可以通过如下3中方式。

方式1:

// 在定义原型Bean时设置proxyMode属性为TARGET_CLASS// 这样容器在启动时就会将当前HttpSecurity注册为代理Bean(ScopedProxyFactoryBean)@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)static class HttpSecurity {}static class SecurityFilterChain {  @Resource  private HttpSecurity httpSecurity ;  public String toString() {    return "SecurityFilterChain [httpSecurity=" + httpSecurity + "]";  }}

方式2:

static class SecurityFilterChain {  @Resource  // 在注入的时候使用@Lazy注解,这样对应的BeanPostProcessor就会创建一个代理ji你先注入  @Lazy  private HttpSecurity httpSecurity ;  @Override  public String toString() {    return "SecurityFilterChain [httpSecurity=" + httpSecurity + "]";  }}

方式3:

static class SecurityFilterChain {  @Resource  private ApplicationContext context ;  public void test() {    // 在每次使用的时候动态获取    HttpSecurity httpSecurity = context.getBean(HttpSecurity.class) ;  }  @Override  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 的问题    @Lookup    protected 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.B  at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)

出现这样的错误,我们可以通过如下方式来解决:

方式1:指定要注入的Bean名称

@Componentstatic class A implements DAO {}@Componentstatic class B implements DAO {}@Componentstatic class Service {  @Resource(name = "a")  private DAO d ;}

方式2:通过限定符

@Componentstatic class A implements DAO {}@Component@Qualifierstatic class B implements DAO {}@Componentstatic class Service {  @Resource  // 指定了注入的bean也必须被@Qualifier标注  // 其实我们也可以自定义注解,但是需要我们注册相应的注解  // 通过QualifierAnnotationAutowireCandidateResolver  @Qualifier  private DAO d ;}

方式3:通过优先级注解@Priority

@Component@Priority(2)static class A implements DAO {}@Component@Priority(1)static class B implements DAO {}@Componentstatic class Service {  @Resource  // 上面优先级高的会被注入,值越小优先级越高  private DAO d ;}

方式4:通过@Primary注解

@Component@Primarystatic 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 ;@Resourceprivate ApplicationContext context ;
@PostConstructpublic 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方法事务

@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionAttributeSource transactionAttributeSource() {  // 设置为false,受保护的及package-private也可以被代理  return new AnnotationTransactionAttributeSource(false) ;}

这种方式前提是你的允许BeanFactory的allowBeanDefinitionOverriding属性设置为true。

问题九、如何让自定义注解标注的类也能被Spring容器所管理?

答:可以通过如下方式。

方式1:

自定义@ComponentScan注解

@ComponentScan(useDefaultFilters = false, includeFilters = {  @Filter(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 {  @Override  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 {  @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("通用方法...") ; } }// 自定义BeanPostProcessor,就是创建代理对象的核心@Componentstatic class CustomAopCreator extends AbstractAutoProxyCreator { @Override protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException { return new Object[] {new DefaultIntroductionAdvisor(new CustomIntroductionInterceptor(), CommonDAO.class)} ; } @Override protected 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接口的能力。

你都答对了几个?欢迎交流讨论

求关注+转发

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832