[Spring] analysis of the underlying core principles of Spring

Content index of this article:

1.Bean Underlying principles of life cycle
2.Underlying principle of dependency injection
3.Initialize underlying principles
4.Inferential construction method and underlying principle
5.AOP Underlying principle
6.Spring Transaction underlying principle

However, they are only general processes. In the follow-up, we will analyze the source code implementation in detail for each process.

Let's take a look at the code for getting started using Spring:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
  • The first line of code will construct a ClassPathXmlApplicationContext object. How should ClassPathXmlApplicationContext be understood? What else will it do to call this construction method in addition to instantiating an object?
  • The second line of code will call the getBean method of ClassPathXmlApplicationContext and get a UserService object. How is getBean() implemented? Is there any difference between the returned UserService object and our own direct new UserService object?
  • The third line of code simply calls the test() method of UserService, which is not difficult to understand.

However, the use of ClassPathXmlApplicationContext is out of date. In the bottom layer of the new version of Spring MVC and Spring Boot, AnnotationConfigApplicationContext is mainly used, such as:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

You can see that the usage of AnnotationConfigApplicationContext is very similar to ClassPathXmlApplicationContext, except that you need to pass in a class instead of an xml file.

AppConfig Class and Spring Like XML, it represents the configuration of Spring. For example, you can specify the scanning path and directly define the Bean, such as:

spring. The contents in the XML are:

<context:component-scan base-package="com.zhouyu"/>
<bean id="userService" class="com.zhouyu.service.UserService"/>

The contents in AppConfig are:

@ComponentScan("com.cry")
public class AppConfig {

 @Bean
 public UserService userService(){
  return new UserService();
 }

}

So spring XML and AppConfig Class is essentially the same.

At present, we rarely use the above method to use Spring directly, but use Spring MVC or Spring Boot. However, they are all based on the above method and need to create an ApplicationContext internally, but:

  • Spring MVC creates XmlWebApplicationContext, which is similar to ClassPathXmlApplicationContext and is configured based on XML
  • Spring Boot creates an AnnotationConfigApplicationContext

In fact, whether AnnotationConfigApplicationContext or ClassPathXmlApplicationContext, at present, we can simply understand them as being used to create Java objects. For example, calling getBean() will create objects (this is not rigorous, and getBean may not create objects, which will be explained later)

In the Java language, an object must be created based on a class. Let's look at the example code:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();

When we call context When getBean ("userService"), an object will be created, but how can the getBean method know that "userService" corresponds to the userService class?

Therefore, we can analyze that when calling the construction method of AnnotationConfigApplicationContext, that is, the first line of code, we will do some things:

  • 1. Parse AppConfig Class to get the scanning path
  • 2. Traverse all Java classes under the scanning path. If @ Component, @ Service and other annotations are found on a class, Spring will record the class and store it in a Map, such as Map < string, class >. (in fact, there is a similar Map in the Spring source code, called BeanDefinitionMap, which will be discussed later)
  • 3.Spring will generate the beanName corresponding to the current class according to a rule, store it in the Map as a key, and the current class as a value

So, but call context When getBean ("userService"), you can find the userService class according to "userService", so you can create the object.

Life cycle of spring bean creation

What is the difference between the bean s created by the spring framework and ordinary object s?

UserService userService = (UserService) applicationContext.getBean("userService");
UserService userService1 = new UserService();

The process that Spring needs to go through to create bean s:
Userservice -- > parameterless constructor -- > object -- > dependency injection (assign value to the attribute with @ Autowired annotation in object) - > bean

Di (dependency injection):

for (Field field: userService.gatClass().getFields()){
    if (field.isAnnotationPresent(Autowired.class)){
        field.set(userService, ??)
    }
}

There are still some steps from dependency injection to the final generation of bean s by Spring: before initialization -- > initialization -- > after initialization

@Component
public class Admin {

	private String username;
	private String password;
}
@Autowired
Admin admin; //New admin() / / MySQL -- > admin object -- > admin attribute

/**
* Just put the time to call this assignment method before bean generation
*/
public void setAttribute(){
//Find out the attributes in mysql here and assign them to the admin object manually
}

We now have an Admin class that maps the data found in mysql. After Spring helps us generate this bean through @ Autowired, it has no attributes. The specific attribute username/password needs to be manually injected by calling setAttribute() method. Is there any way to automatically call setAttribute() method before bean generation to make Spring do DI? There must be.

We just need to perform this process before the initialization of the bean life cycle (setAttribute).

@PostConstruct
public void setAttribute(){
//Find out the attributes in mysql here and assign them to the admin object manually
}

We only need to add a @ PostConstruct annotation to the method, and Spring knows to call this method before bean initialization.

Automatic assignment of DI injected objects:

for (Method method: userService.gatClass().getMethods()){
    if (method.isAnnotationPresent(PostConstruct.class)){
        method.invoke(userService,null);
    }
}

You can also implement similar functions by implementing InitializingBean and overriding the afterpropertieset() method. The only difference is that Spring determines whether the current class implements the InitializingBean interface during initialization (afterpropertieset)

public class UserService implements InitializingBean{
	@Override
	public void afterPropertiesSet() throws Exceptions{

	}
}

Judge whether an object implements an interface: object instantof InterfaceName

if (object instant of InitializingBean){
	(InitializingBean)object.afterPropertiesSet();
}

Spring source code: search doCreateBean, enter initializebean (beanname, exposed object, MBD), enter invokeinitialmethods (beanname, wrapped bean, MBD),

Then it goes through: after initialization (AOP) - > proxy object - > bean, and finally gets a bean

To sum up, the life cycle of a SpringBean creation is:

BeanClass – > parameterless construction method (inferred construction method) - > ordinary object – > dependency injection (assign value to the attribute with @ Autowired annotation in the object) - > before initialization (call the method with @ PostConstruct annotation) - > initialization (judge the object instant of initializeBean and call the rewritten afterpropertieset) - > after initialization (get the proxy object from AOP) - > springbean

Let's carefully analyze some details in the process of creating SpringBean life cycle:

Detail 1: inferred construction method

public UserService() {    //1
   System.out.println("1");
}

public UserService(OrderService orderService) {  //2 
   this.orderService = orderService;
   System.out.println("2");
}

public UserService(OrderService orderService,OrderService orderService1) {  //3
   this.orderService = orderService;
   System.out.println("3");
}

For example, we now have three construction methods for UserService, and Spring will choose the parameterless construction method by default. If we comment out the default parameterless construction method now, how will Spring choose?

 No default constructor found; nested exception is java.lang.NoSuchMethodException: com.zhouyu.service.UserService.<init>()

Yes, he will report an exception, because when there are only two parameterized construction methods, Spring doesn't know which parameterized construction method to choose. He will try to find the default constructor, but he didn't find it, so he reported this exception. You can make Spring recognize correctly by adding @ Autowired annotation to the constructor you want Spring to call.

Detail 2: Bean creation process

When we have only one parameter constructor, does its input parameter have a value? If so, when was it created?

public UserService(OrderService orderService) {  //2 
   this.orderService = orderService;
   System.out.println("2");
}

We can see that the input parameter has value. How does this object come from? It must be from the Spring container

Map<name,orderService> map // Check by name
for(int i=0;i<map.size();i++){
    if(map.get(name).getClass() == object.getClass){  // Check by type
        count++;
    }
}
if(count == 1){
    return map.get(name);
}

Detail 3: AOP general process

Call userService. after AOP is turned on. Test(), you will see that the orderService attribute in userservice is null, but after entering the test() method, you will see that the orderService in userservice has a value. Why?

After analysis, it can be found that it is "unnecessary"

The core idea of cglib dynamic proxy is to inherit the parent class of the target class:

public class CglibProxy extends UserService {

	private UserService target;

	/**
	 * target assignment
	 *
	 * @param wrappedBean Common object
	 */
	public void applyBeanPostProcessorsAfterInitialization(Object wrappedBean) {
		target = (UserService) wrappedBean;
	}

	@Override
	public void test() {
		// Execute slice logic of @ Before
		target.test();
		// Execute slice logic of @ After
	}
}

The core idea of jdk dynamic agent is through class forName(). GetMethod () gets the method that needs agent in this class, then calls method.. Invoke() reflection calls the target method:

public class JdkProxy {

	private static Method m;

	/**
	 * JDK The dynamic proxy class will first get all the methods in the static code block through reflection and store them in the static variables
	 */
	static {
		try {
			m = Class.forName("service.IUserService")
					.getMethod("test",new Class[]{});
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public void test(){
		try {
			// todo: before enhanced logic
			m.invoke(this,null);
			// todo: after enhanced logic
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

}

Implementation of Cglib in Spring:

public class CglibDynProxy implements Callback {

    /**
     * Target object to proxy
     */
    private Object target;

    /**
     * Proxy logic: generate subclasses of the target class through bytecode
     */
    public Object proxy(){
        // 1. Create proxy enhancement object
        Enhancer enhancer = new Enhancer();
        // 2. Set the parent class, that is, the proxy target class. CGLIB implements dynamic proxy by generating subclasses of proxy class
        enhancer.setSuperclass(target.getClass());
        // 3. Set the callback function. this is actually the agent logic implementation class, that is, the aspect, which is equivalent to the InvocationHandler of JDK dynamic agent
        enhancer.setCallback(this);
        // 4. Create a proxy object, that is, a subclass of the target class
        return enhancer.create();
    }
}

Implementation of jdk dynamic proxy in Spring:

public class JdkDynProxy {

	private static Method m;

	/**
	 * JDK The dynamic proxy class will first get all the methods in the static code block through reflection and store them in the static variables
	 */
	static {
		try {
			m = Class.forName("service.IUserService")
					.getMethod("test",new Class[]{});
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	private InvocationHandler getInvocationHandler(){
		return (InvocationHandler) new JdkDynProxy();
	}

	public final void test() throws Throwable {
		InvocationHandler invocationHandler = getInvocationHandler();
		invocationHandler.invoke(this,m,new Object[]{});
	}

}

Create userserviceproxy class - > generate userserviceproxy proxy object - > proxy object target = normal object

In the applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName) method, Spring passes in the ordinary object filled with DI attributes, and then creates an AOP proxy object so that its target attribute = wrappedBean;

Overall creation process of AOP section:

Traverse all @ Aspect annotated classes@ Before@After And other annotation methods, take out all of them to match whether they are related to the Target class, and then throw them into a Map container for caching, which can be used directly when the code runs.

Detail 4: Spring transactions

After @ Transactional starts a transaction on a method, you need to add @ Configuration annotation on the startup class, otherwise the transaction cannot take effect normally. Why?

public class CglibProxy extends UserService {

	private UserService target;

	/**
	 * target assignment
	 *
	 * @param wrappedBean Common object
	 */
	public void applyBeanPostProcessorsAfterInitialization(Object wrappedBean) {
		target = (UserService) wrappedBean;
	}

	@Override
	public void test() {
		// 1. Judge whether there is @ Transactional annotation on the method
        // 2. Create a database connection conn (created by the transaction manager through dataSource)
        // 3.conn.autocommit = false
		target.test();
        // If no exception is thrown in the method: conn.commit()
        // If an exception is thrown in the method: conn.rollback()
	}
}

Criteria for judging whether Spring transactions will fail: when a method annotated with @ Transactional is called, it is necessary to judge whether it is directly called by the proxy object. If so, the transaction will take effect, and if not, it will fail.

It can be solved by creating another class and injecting; Or it can be solved by injecting the current class into the current class.

Back to the initial question, why can't the transaction be started normally without adding @ Configuration or @ Component to the Configuration class?

Because Spring uses the proxy mode, Spring will generate an AppConfig proxy object. The proxy will first go to the Spring container to find the dataSource, if any, and return it directly. If not, it will really call the dataSource() method in the AppConfig class. If there is no @ Configuration or @ Component annotation, the dataSource used by JDBC template and transactionManager are two objects, and they are not related, so we can't be affected by transactionManager when using JDBC template to operate the database.

Keywords: Java Spring

Added by roel_v on Sat, 19 Feb 2022 11:02:25 +0200