08 Question Scene Spring Core Thoughts Question Collection

08 Question Scene Spring Core Thoughts Question Collection #

Hello, I’m Fu Jian.

If you’re reading this article, then I’m really happy. It means that you’ve followed along with the content of the first chapter and have also looked into the post-lesson thinking questions. I want to manually give you a thumbs up for being able to continue learning and keeping a lifelong learning attitude amidst busy work. I believe we are kindred spirits.

So far, we have already studied 17 cases and solved quite a few problems. I wonder how you feel about it and what you have gained?

I still remember a very interesting comment from a student in the comment section of the opening. He said, “As a frontline bug creator, I hope to write fewer bugs.” I can totally relate. Over the years, I have often struggled with Spring in my battle of wits, and have sometimes become anxious when I couldn’t solve certain problems in a timely manner. But in the end, I found it quite interesting. This column can also be seen as a culmination of my experiences, and I hope it can provide you with some practical help.

Initially, I actually wanted to discuss the thinking questions from the previous lesson with you in each lesson. However, I was concerned that everyone’s learning progress would be different, so I decided to have this centralized Q&A session. I will provide you with my answers, and you can also compare them and see if there are better solutions. Feel free to contribute your own “options” and let’s exchange ideas together. I hope everyone can gain some positive feedback from problem-solving and complete the learning cycle.

Lesson 1 #

In Case Study 2, when a constructor is explicitly defined, it will attempt to find the corresponding Bean based on the constructor parameters. Here, I’d like you to consider a question: if the corresponding Bean cannot be found, will it always result in an error like in Case Study 2?

The answer is actually no. Let’s modify the code in Case Study 2, as follows:

@Service
public class ServiceImpl {
    private List<String> serviceNames;
    public ServiceImpl(List<String> serviceNames){
        this.serviceNames = serviceNames;
        System.out.println(this.serviceNames);
    }
}

In the above code, we have changed the constructor parameter from a normal String to a List. When we run the program, we will find that it does not produce an error, but instead outputs [].

To understand this phenomenon, we can directly locate the code that constructs the constructor call parameters (i.e., ConstructorResolver#resolveAutowiredArgument):

@Nullable
protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
      @Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {

   // Omit irrelevant code
   try {
      // Find the bean based on the constructor parameter
      return this.beanFactory.resolveDependency(
            new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
   }
   catch (NoUniqueBeanDefinitionException ex) {
      throw ex;
   }
   catch (NoSuchBeanDefinitionException ex) {
      // If the "bean" cannot be found, fallback occurs
      if (fallback) {
         // Single constructor or factory method -> let's return an empty array/collection
         // for e.g. a vararg or a non-null List/Set/Map parameter.
         if (paramType.isArray()) {
            return Array.newInstance(paramType.getComponentType(), 0);
         }
         else if (CollectionFactory.isApproximableCollectionType(paramType)) {
            return CollectionFactory.createCollection(paramType, 0);
         }
         else if (CollectionFactory.isApproximableMapType(paramType)) {
            return CollectionFactory.createMap(paramType, 0);
         }
      }
      throw ex;
   }
}

When a suitable Bean cannot be found for a parameter of a collection type, it does not immediately throw an error, but instead attempts to fallback. In this case, the following statement is used to create an empty collection as the constructor parameter:

CollectionFactory.createCollection(paramType, 0);

The final code that gets executed is as follows:

return new ArrayList<>(capacity);

So it is evident that the final modified case does not produce an error, but rather sets serviceNames to an empty List. From this, we can see that autowiring is far more complicated than you might imagine.

Lesson 2 #

We have learned that we can reference the desired bean using @Qualifier, or directly name the attribute as the bean name. These two ways are as follows:

// Way 1: Name the attribute the same as the bean to be wired
@Autowired
DataService oracleDataService;

// Way 2: Use @Qualifier for direct reference
@Autowired
@Qualifier("oracleDataService")
DataService dataService;

So, for the inner class reference in Case 3, do you think it can be achieved using the first way? For example, using the following code:

@Autowired- DataService studentController.InnerClassDataService;

In fact, if you try it or if we are a bit more perceptive, we will find that the code itself cannot be compiled because there is a period in the middle. Is there any way to reference the inner class using this approach?

Looking at the source code that determines the priority, the execution situation that uses the attribute name to match can be referred to the debugging view of the DefaultListableBeanFactory#matchesBeanName method:

As we can see, the key implementation actually lies in the following line of code:

candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName))

Clearly, our bean has not been given an alias, and since the attribute name cannot contain “.”, it cannot match a bean name with “.” (i.e., studentController.InnerClassDataService).

Therefore, if an inner class does not explicitly specify a name or alias, it is not possible to reference the corresponding bean by using a consistent attribute name and bean name.

Lesson 3 #

In case 2, the initial result we obtained when running the program is as follows:

[Student(id=1, name=xie), Student(id=2, name=fang)]

So, how can we make student 2 output first?

Actually, in case 2, the target type we collected is List, and List is sortable. So how is it sorted exactly? In the analysis of case 2, we provided the code for DefaultListableBeanFactory#resolveMultipleBeans method, but some non-essential code was omitted, including the sorting work. The code is as follows:

if (result instanceof List) {
   Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
   if (comparator != null) {
      ((List<?>) result).sort(comparator);
   }
}

And for the final sorting in this case, OrderComparator#doCompare method is executed, the key code is as follows:

private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
   boolean p1 = (o1 instanceof PriorityOrdered);
   boolean p2 = (o2 instanceof PriorityOrdered);
   if (p1 && !p2) {
      return -1;
   }
   else if (p2 && !p1) {
      return 1;
   }

   int i1 = getOrder(o1, sourceProvider);
   int i2 = getOrder(o2, sourceProvider);
   return Integer.compare(i1, i2);
}

The execution of getOrder, getting the order value (equivalent to priority), is obtained through AnnotationAwareOrderComparator#findOrder:

protected Integer findOrder(Object obj) {
   Integer order = super.findOrder(obj);
   if (order != null) {
      return order;
   }
   return findOrderFromAnnotation(obj);
}

It is not difficult to see that getting the order value includes two methods:

  1. Getting the value from @Order, referring to AnnotationAwareOrderComparator#findOrderFromAnnotation:
@Nullable
private Integer findOrderFromAnnotation(Object obj) {
   AnnotatedElement element = (obj instanceof AnnotatedElement ? (AnnotatedElement) obj : obj.getClass());
   MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY);
   Integer order = OrderUtils.getOrderFromAnnotations(element, annotations);
   if (order == null && obj instanceof DecoratingProxy) {
      return findOrderFromAnnotation(((DecoratingProxy) obj).getDecoratedClass());
   }
   return order;
}
  1. Getting the value from Ordered interface implementation method, referring to OrderComparator#findOrder:
protected Integer findOrder(Object obj) {
   return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
}

Through the analysis above, if we cannot change the class inheritance relationship (for example, making Student implement the Ordered interface), we can adjust the order by using @Order. The specific code modification is as follows:

@Bean
@Order(2)
public Student student1(){
    return createStudent(1, "xie");
}

@Bean
@Order(1)
public Student student2(){
    return createStudent(2, "fang");
}

Now, we can reverse the original output order of the beans, as shown below:

Student(id=2, name=fang), Student(id=1, name=xie)

Lesson 4 #

In Case 2, the class LightService. We can inject it into the Spring container automatically using the @Service annotation, instead of using the Bean method in the Configuration annotation class. At the same time, we implement the Closeable interface. The code is as follows:

import org.springframework.stereotype.Component;
import java.io.Closeable;
@Service
public class LightService implements Closeable {
    public void close() {
        System.out.println("turn off all lights");
    }
    // omitted non-essential code
}

Will the close() method of the interface be executed automatically when the Spring container is destroyed?

The answer is yes. Through the analysis of Case 2, you can know that when LightService is a singleton bean that implements the Closeable interface, a DisposableBeanAdapter will be added.

But which method will be executed specifically? shutdown() or close()? You can find the answer in the code. In the inferDestroyMethodIfNecessary method of the DisposableBeanAdapter class, we can see that there are two situations where the close() method of the current bean class will be obtained.

The first situation is when we use @Bean and use the default destroyMethod property (INFER_METHOD) mentioned in this lesson. The second situation is to check whether the current class implements the AutoCloseable interface. If it does, then the close() method of this class will definitely be obtained.

private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
   String destroyMethodName = beanDefinition.getDestroyMethodName();
   if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) || (destroyMethodName == null && bean instanceof AutoCloseable)) {
      if (!(bean instanceof DisposableBean)) {
         try {
            return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
         }
         catch (NoSuchMethodException ex) {
            try {
               return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
            }
            catch (NoSuchMethodException ex2) {
               // no candidate destroy method found
            }
         }
      }
      return null;
   }
   return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}

By now, I believe that you should be able to make the shutdown method execute in combination with the Closeable interface and the @Service (or other @Component) annotation.

Lesson 5 #

In Case 2, we mentioned three ways to instantiate a class using reflection:

  • java.lang.Class.newInstance()
  • java.lang.reflect.Constructor.newInstance()
  • sun.reflect.ReflectionFactory.newConstructorForSerialization().newInstance()

Among them, the third way does not initialize class attributes. Can you provide an example to prove this point?

Here is an example that demonstrates it:

import sun.reflect.ReflectionFactory;
import java.lang.reflect.Constructor;

public class TestNewInstanceStyle {

    public static class TestObject{
        public String name = "fujian";
    }

    public static void main(String[] args) throws Exception {
        // Using ReflectionFactory.newConstructorForSerialization()
        ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
        Constructor constructor = reflectionFactory.newConstructorForSerialization(TestObject.class, Object.class.getDeclaredConstructor());
        constructor.setAccessible(true);
        TestObject testObject1 = (TestObject) constructor.newInstance();
        System.out.println(testObject1.name);
        
        // Regular way
        TestObject testObject2 = new TestObject();
        System.out.println(testObject2.name);
    }

}

The output is as follows:

null - fujian

Lesson 6 #

In fact, when reviewing the revision plans of the two cases in this lesson, you will find that although the changes are minor, they are not elegant enough. So, is there a more elegant alternative? If so, do you know the underlying principles and key source code? By the way, you can also think about why I didn’t use a more elegant solution.

We can move the enhanced method “未达到执行顺序预期” to a separate aspect class, and different aspect classes can be decorated with @Order. The smaller the value of @Order, the higher the execution priority. Taking case 2 as an example, we can modify it as follows:

@Aspect
@Service
@Order(1)
public class AopConfig1 {
    @Before("execution(* com.spring.puzzle.class6.example2.ElectricService.charge()) ")
    public void validateAuthority(JoinPoint pjp) throws Throwable {
        throw new RuntimeException("authority check failed");
    }
}


@Aspect
@Service
@Order(2)
public class AopConfig2 {

    @Before("execution(* com.spring.puzzle.class6.example2.ElectricService.charge())")
    public void logBeforeMethod(JoinPoint pjp) throws Throwable {
        System.out.println("step into ->"+pjp.getSignature());
    }

}

The core of the above modification is to divide the original AOP configuration into two classes and mark their priority with @Order respectively. After this modification, when the authorization fails, the related log message “step into ->” will not be printed.

Why is this feasible? Let’s trace back to case 1. At that time, we proposed a conclusion: when AbstractAdvisorAutoProxyCreator is executing findEligibleAdvisors (as shown in the following code), the order of Advisors to be returned is determined by two factors: the order of candidateAdvisors and the sorting performed by sortAdvisors.

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
   List<Advisor> candidateAdvisors = findCandidateAdvisors();
   List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
   extendAdvisors(eligibleAdvisors);
   if (!eligibleAdvisors.isEmpty()) {
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
   }
   return eligibleAdvisors;
}

At that time, the key problem that caused errors in our case was the order of candidateAdvisors, so we focused on it. On the other hand, there was not much mention about the sorting performed by sortAdvisors. Here, I can provide more details on this.

Implementation-wise, the execution of sortAdvisors ultimately calls the compare() method of the comparator class AnnotationAwareOrderComparator, which uses the return value of getOrder() as the sorting criterion:

public int compare(@Nullable Object o1, @Nullable Object o2) {
   return doCompare(o1, o2, null);
}

private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
   boolean p1 = (o1 instanceof PriorityOrdered);
   boolean p2 = (o2 instanceof PriorityOrdered);
   if (p1 && !p2) {
      return -1;
   }
   else if (p2 && !p1) {
      return 1;
   }

   int i1 = getOrder(o1, sourceProvider);
   int i2 = getOrder(o2, sourceProvider);
   return Integer.compare(i1, i2);
}

Continuing to trace the execution details of getOrder(), we will find that for our case, this method determines the Order value of the configured aspect Bean. You can refer to the debugging view of BeanFactoryAspectInstanceFactory#getOrder to verify this conclusion:

In the above screenshot, aopConfig2 is the name of the configured aspect Bean. Here, I also provide a screenshot of the call stack for further research:

Now we know that placing different enhanced methods in different aspect configuration classes and using different Order values to modify them can affect the order. Conversely, if they are all in one configuration class, they will naturally not affect the order. This is also why the sortAdvisors method was not emphasized in my original solution, because all the examples we provided at that time only had one AOP configuration class.

Lesson 7 #

In Case 3, we mentioned that the default event execution happens in the same thread as the event publisher, which can be evidenced by the following logs:

2021-03-09 09:10:33.052 INFO 18104 — [nio-8080-exec-1] c.s.p.listener.HelloWorldController : start to publish event- 2021-03-09 09:10:33.055 INFO 18104 — [nio-8080-exec-1] c.s.p.l.example3.MyFirstEventListener : com.spring.puzzle.class7.example3.MyFirstEventListener@18faf0 received: com.spring.puzzle.class7.example3.MyEvent[source=df42b08f-8ee2-44df-a957-d8464ff50c88]

From the logs, we can see that the event publishing and execution both use the same thread nio-8080-exec-1. However, when we have multiple events, we often want the events to be executed faster or asynchronously without affecting the main thread. What can we do in this case?

To meet the requirements mentioned above, we only need to introduce a thread pool for event execution. Let’s first see how Spring supports this. In fact, in the analysis of Case 3, we have already included the following code snippet (located in the multicastEvent method of SimpleApplicationEventMulticaster):

// Omitted irrelevant code
// Get the executor
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    // If the executor exists, submit the task to the executor for execution
    if (executor != null) {
        executor.execute(() -> invokeListener(listener, event));
    }
}
// Omitted irrelevant code

For event handling, we can bind an Executor to execute the task. How can we bind it? Actually, it’s similar to the way we bind the ErrorHandler as mentioned in this lesson. The code for binding is as follows:

// Note that the statement below can only be executed once to avoid creating thread pools repeatedly
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
// Omitted irrelevant code
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = applicationContext.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, SimpleApplicationEventMulticaster.class);
simpleApplicationEventMulticaster.setTaskExecutor(newCachedThreadPool);

We can retrieve the SimpleApplicationEventMulticaster and directly call the corresponding set() method to set the thread pool. With the program modified in this way, the event handling logs are as follows:

2021-03-09 09:25:09.917 INFO 16548 — [nio-8080-exec-1] c.s.p.c.HelloWorldController : start to publish event- 2021-03-09 09:25:09.920 INFO 16548 — [pool-1-thread-3] c.s.p.l.example3.MyFirstEventListener : com.spring.puzzle.class7.example3.MyFirstEventListener@511056 received: com.spring.puzzle.class7.example3.MyEvent[source=cbb97bcc-b834-485c-980e-2e20de56c7e0]

As we can see, the event publishing and handling are executed in different threads, namely nio-8080-exec-1 and pool-1-thread-3, respectively, which meets our requirements.

That’s all for this Q&A. See you in the next chapter!