[Summary of Java Interview Questions] Spring Chapter (2023 Edition)

 navigation:

[Dark Horse Java Notes + Stepping on the Pit Summary] JavaSE+JavaWeb+SSM+SpringBoot+Riji Takeaway+SpringCloud+Dark Horse Travel+Grain Mall+Xuecheng Online+Common Interview Questions

Table of contents

1. A brief introduction to Spring

2. Talk about your understanding of IOC

3. Talk about your understanding of AOP

4. Talk about the life cycle of Bean

5. Talk about circular dependencies and L3 cache

6. Talk about several registration methods of Bean

7. Talk about the scope of Bean, and the default scope

8. Talk about the difference between BeanFactory and FactoryBean

9. Talk about the difference between @Autowired and @Resource annotations

10. Talk about the two transaction models of Spring

11. Talk about 11 scenarios where declarative transactions fail

12. How to solve the multi-threaded transaction failure problem?

13. Why is it not recommended to use declarative transactions?

14. Talk about the isolation level of Spring transactions

15. Talk about the propagation behavior of Spring transactions

16. What happens if the database goes down during transaction execution?

17. What happens if the transaction rollback fails?


1. A brief introduction to Spring

Scoring points:

IOC, Bean, DI, AOP, transaction

Why use Spring?

  • Lightweight and simplified development: Spring is a lightweight application framework, the basic version is only 2M. The purpose of Spring is to simplify the development of enterprise-level applications.
  • Core functions: IOC/DI/AOP/transaction: Developers only need to focus on business requirements, not bean management. Spring effectively reduces coupling through IOC and DI, and effectively reduces code intrusion through aop; transactions are highly customizable and support multiple isolation levels and propagation strategies.
  • SpringMVC: It is a sub-module of Spring that provides more powerful and flexible web framework support. Its working principle is mainly to dispatch and process requests through DispatcherServlet (scheduling controller).
  • Easy to integrate: third-party frameworks such as Mybatis can be easily integrated, just need to import dependencies and simple configuration. For example, integrating Mybatis only needs to import the mybatis-spring package, and register DataSource, SqlSessionFactoryBean, and MapperScannerConfigurer as beans.
  • Open source, flexible community: Spring has a large community that provides rich documentation, sample code, extension libraries, and more. Frameworks such as springboot, SpringCloud, and SpringSecurity are all implemented based on Spring

Spring is a framework for simplifying web development, microservices, distributed development, transaction processing and integration with other frameworks.

The idea of ​​IOC inversion of control: the control right to create an object is reversed from the inside (that is, new instantiation) to the outside (that is, the IOC container).

Bean: Objects stored in the IOC container

DI Dependency Injection: Bind the dependencies between beans in the IOC container. For example, inject the dao layer object into the service layer object.

AOP aspect-oriented programming: to enhance the code without changing the original code. Extract the common code (that is, notification) and implant it into the method to be enhanced (that is, the entry point). The relationship between the notification and the pointcut is the aspect, and the enhanced method is to create a proxy object enhanced by the original object.

Transaction: Ensure that a series of database operations in the data layer or business layer succeed and fail at the same time

standard answer

The Spring framework includes many modules, such as Core, Testing, Data Access, Web Servlet, etc., among which Core is the core module of the entire Spring framework .

The Core module provides a series of basic functions such as IoC container, AOP function, data binding, type conversion, etc., and the functions of these functions and other modules are based on IoC and AOP, so IoC and AOP are Spring framework core .

IoC  Inversion of Control

The control right to create an object is reversed from the internal (new instantiation) to the external (that is, the IOC container). This idea is called inversion of control. When using an object, the object generated by active new is converted to an object provided by the outside.

IoC (Inversion of Control) means inversion of control, which is a design idea of ​​object-oriented programming. Without adopting this idea, we need to maintain the dependencies between objects by ourselves, which can easily lead to excessive coupling between objects, which is very unfavorable for code maintenance in a large-scale project. IoC can solve this problem, it can help us maintain the dependencies between objects and reduce the coupling between objects.

DI dependency injection

When it comes to IoC, you have to say DI (Dependency Injection). DI means dependency injection. It is the way IoC is implemented, that is to say, IoC is implemented through DI . Since the term IoC is more abstract but DI is more intuitive, we often use DI to replace it. In many cases, we simply equate IoC and DI, which is a habit. The key to realizing dependency injection is the IoC container , which is essentially a factory.

AOP  aspect-oriented programming

AOP (Aspect Oriented Programming) is an aspect-oriented programming idea. This idea is a supplement to OOP. It can further improve the efficiency of programming on the basis of OOP . Simply put, it can unify the common requirements of a batch of components (such as permission checking, logging, transaction management, etc.). Under the idea of ​​AOP, we can separate the code that solves common requirements , and then declare where and when to call these codes through configuration . When the calling conditions are met, AOP will implant the business code into the location we specified, thus solving the problem uniformly without modifying the code of this batch of components.

reference:

Spring Foundation 1 - Spring (configuration development version), IOC and DI_vincewm's blog - CSDN blog

Spring Foundation 3 - AOP, transaction management_spring aop transaction management_vincewm's blog-CSDN blog

2. Talk about your understanding of IOC

scoring points

Inversion of Control and Dependency Injection Implications

standard answer

The idea of ​​IOC inversion of control: the control right to create an object is reversed from the inside (that is, new instantiation) to the outside (that is, the IOC container).

Bean: Objects stored in the IOC container

DI Dependency Injection: Bind the dependencies between beans in the IOC container. For example, inject the dao layer object into the service layer object.

IoC means inversion of control and is a design idea of ​​object-oriented programming. Without adopting this idea, we need to maintain the dependencies between objects by ourselves, which can easily lead to excessive coupling between objects, which is very unfavorable for code maintenance in a large-scale project. IoC can solve this problem, it can help us maintain the dependencies between objects and reduce the coupling between objects.

When it comes to IoC, you have to talk about DI. DI means dependency injection, which is the way IoC is implemented. Since the term IoC is more abstract and DI is more intuitive, we often use DI to replace it. In many cases, we simply equate IoC and DI, which is a habit. The key to implementing dependency injection is the IoC container, which is essentially a factory.

Bonus answer

IoC is a powerful tool for complex relationships between even components in Java EE enterprise application development.

Before the lightweight Java EE development represented by Spring became popular, more development models represented by EJB were used in actual development. In the EJB development mode, developers need to write EJB components, which need to meet the EJB specification to run in the EJB container, so as to complete basic services such as transaction acquisition and life cycle management. The services provided by Spring are no different from EJB. It’s just that the design of the two is very different in how to obtain services: Spring IoC provides a basic JavaBean container, manages dependencies through IoC mode, and enhances POJO objects such as JavaBean through dependency injection and AOP aspect It serves basic functions such as transaction management and life cycle management; for EJB, a simple EJB component needs to write remote/local interface, Home interface and Bean entity classes, and EJB operation cannot be separated from the EJB container, and it is also necessary to find other EJB components Through methods such as JNDI, this creates a dependence on the EJB container and technical specifications. That is to say, Spring restores EJB components to POJO objects or JavaBean objects, thereby reducing the dependence of user development on traditional J2EE technical specifications.

In application development, developers often need to reference and call the services of other components when designing components. If this dependency is solidified in component design, it will cause rigidity of dependencies and increase the difficulty of maintenance. At this time, use IoC to obtain resources. The direction is reversed, let the IoC container actively manage these dependencies, and inject these dependencies into components, which will make the adaptation and management of these dependencies more flexible.

3. Talk about your understanding of AOP

scoring points

AOP concept, AOP function, AOP implementation

standard answer

AOP aspect-oriented programming: extract the common code (ie notification) and implant it into the method to be enhanced (ie pointcut).

Function: Enhance the code without changing the original code.

Core idea:

  • Connection point: Every method in a bean can be a connection point;
  • Entry point: the method to be enhanced in the bean;
  • Notifications: common methods, enhanced content;
  • Notification class: the Bean where the notification is located;
  • Aspect: the relationship between advice and pointcut.

The underlying implementation of AOP: 

  • JDK dynamic proxy: create a proxy instance of the interface at runtime, based on the reflection mechanism. This proxy is used when the target object has an interface implemented.
    • The original and proxy objects implement the proxy interface, and the simple factory class returns the enhanced proxy object through Proxy.newProxyInstance() . Use the Java reflection mechanism to dynamically generate proxy objects in memory at runtime. Spring AOP adopts the JDK dynamic proxy method to dynamically create proxy objects at runtime to achieve enhancement.
  • CGLib code generation library dynamic proxy: create instances of subclass proxy at runtime, based on ASM framework and bytecode files. This proxy is used when the target object does not implement any interface.
    • The simple factory class implements the MethodInterceptor interface to rewrite the intercept() method enhancement, and returns the instance through the Enhancer tool class. Construct a subclass object in memory to realize the function extension of the target object. The bottom layer is to convert the bytecode and generate new classes by using the ASM framework. ASM can generate bytecode directly, or modify existing bytecode by accessing it.

Advice type: pre-, post-, surround (@Around("pt()") and ProceedingJoinPoint parameters), post-exception, post-return notification

Application scenarios: logs and transactions.

AOP is a kind of programming idea, which is a technology to dynamically and uniformly add functions to programs without modifying the source code through pre-compilation and runtime dynamic proxy . Object-oriented programming abstracts the program into objects at various levels, while aspect-oriented programming abstracts the program into various aspects.

The so-called aspect is equivalent to the cross-cutting point between application objects, and we can abstract it into a separate module. AOP technology uses a technique called "cross-cutting" to dissect the inside of the encapsulated object, encapsulate the public behavior that affects multiple classes into a reusable module, and name it as an aspect. The so-called aspect , in simple terms, is the logic that has nothing to do with the business, but is commonly called by the business modules. Encapsulating it can reduce the duplication of code in the system, reduce the coupling degree of modules, and make use of future operability and maintainability. .

AOP can be used to isolate various parts of business logic, thereby reducing the coupling degree between various parts of business logic, improving program reusability, and improving development efficiency at the same time.

AOP can be implemented in a variety of ways , and Spring AOP supports the following two implementations.

- JDK dynamic proxy: This is a dynamic proxy technology provided by Java, which can create a proxy instance of an interface at runtime . Spring AOP adopts this method by default , weaving code in the proxy instance of the interface.

- CGLib (Code Generation Library) dynamic proxy: uses the underlying bytecode technology to create an instance of a subclass proxy at runtime . When the target object does not have an interface , Spring AOP will use this method to weave the code in the subclass instance.

Bonus answer - application scenario

In terms of application scenarios, Spring AOP provides more convenience for the use of IoC. On the one hand, the application can directly use the functions of AOP, design the cross-cutting concerns of the application, and abstract the functions spanning multiple modules of the application. Through the simple use of AOP, it can be flexibly compiled into modules, for example, the log function in the application can be realized through AOP . On the other hand, within Spring, some supporting modules such as transaction processing are also implemented through Spring AOP .

Classes that AOP cannot enhance:

1. Spring AOP can only enhance the beans in the IoC container , and cannot enhance the objects that are not managed by the container.

2. Since CGLib generates proxy objects by dynamically creating subclasses, it cannot proxy final modified classes.

4. Talk about the life cycle of Bean

scoring points

The four major parts of the Bean life cycle and detailed steps

There are four parts to the life cycle: Bean definition, Bean initialization, Bean lifetime, and Bean destruction.

Spring life cycle:

  • Scan Bean: After Spring starts, scan all .class files under the scanning path configured by the @ComponentScan annotation, and the class loader loads and obtains the Class object of the class according to the class name, and finds out the Bean by judging @Component and other annotations; create for each Bean The BeanDefinition object stores information such as Class objects and scopes, and puts it into the beanDefinitionMap. The mapping relationship is "Bean name" ---> "corresponding BeanDefinition".
  • Traverse the beanDefinitionMap and create all beans. The creation process of each bean is as follows:
  • Inferred constructor:
    • Get the Class object according to BeanDefinition.
    • If the BeanDefinition is bound to a Supplier, then call the Supplier's get method to get an object and return it directly.
    • If factoryMethodName exists in BeanDefinition, then call the factory method to get a bean object and return it.
    • If the BeanDefinition has been automatically constructed, call autowireConstructor() to automatically construct an object.
    • If there is a constructor, use that constructor to instantiate the object;
    • If there are multiple construction methods, and only one construction method is annotated with @Autowired, then this construction method is selected to instantiate the object.
    • If there are multiple construction methods, and none of them are annotated with @Autowired, use the no-argument construction method to instantiate the object. If they all have parameters and there are 0 or more constructors annotated with @Autowired, an error "Bean initialization exception" will be reported.
  • Instantiation: Obtain the inferred constructor object based on reflection, instantiate the class through the newInstance(xx) method of the Constructor object, and obtain the original object;
  • Stored in the third-level cache: Generate Lambda expressions based on the original object and put them into the third-level cache singletonFactories. If this Lambda expression is executed in the future, a proxy object will be generated.
  • Attribute filling: inference and instantiation, storing in the third-level cache, attribute filling (singleton pool lookup, judging circular dependency, second-level cache lookup, third-level cache lookup, generation of proxy objects in advance), execution of third-level cache, and deposit into single example pool
    • Inject by name; get all properties of the bean based on reflection, open private member access restrictions for properties annotated with @Autowired, and fill the property with its Bean object through the getBean() method (which solves the singleton circular dependency).
    • Inject by type;
  • Handle Aware callback: If the Bean instance implements the BeanNameAware interface (judged by instanceof), call the setBeanName() method rewritten by the Bean to assign a value to the beanName variable of the Bean instance.
  • Execute the pre-initialization methods of all BeanPostProcessors: traverse the list of all BeanPostProcessor implementation classes, and execute the postProcessBeforeInitialization() method in each BeanPostProcessor object. This method can implement dynamic proxy through JDK's Proxy.newProxyInstance() to return the proxy object of the target object.
  • Initialization: If the Bean instance implements the InitializingBean interface (judged by instanceof), call the afterPropertiesSet() method rewritten by the Bean to process the initialization logic. afterPropertiesSet translates to "after properties are populated"
  • Execute all BeanPostProcessor post-initialization methods: traverse the list of all BeanPostProcessor implementation classes, and execute the postProcessAfterInitialization() method in each BeanPostProcessor object. This method can implement dynamic proxy through JDK's Proxy.newProxyInstance() to return the proxy object of the target object.
  • Store in the singleton pool: If it is a singleton, store the final proxy object in the singleton pool (first-level cache, singletonObjects).
  • Lifetime: The Bean is ready to be used, and all Beans will remain in the application context until the application context is destroyed.
  • Destruction: If the bean implements the DisposableBean interface, Spring will call its destroy() interface method. Similarly, if there is a method annotated with @PreDestroy, this method will also be called.

The Bean life cycle is roughly divided into four parts: Bean definition, Bean initialization, Bean lifetime and Bean destruction.

  1. Create beans: Spring starts, finds and loads beans that need to be managed by Spring, and instantiates beans
  2. Attribute filling: After the Bean is instantiated, the Bean's reference and value are injected into the Bean's attributes
  3. BeanNameAware: If the Bean implements the BeanNameAware interface, Spring passes the Bean name to the setBeanName() method
  4. @PostConstruct: If you customize the initialization method and add the @PostConstruct annotation before the method, Spring will execute this method
  5. BeanFactoryAware: If the Bean implements the BeanFactoryAware interface, Spring will call the setBeanFactory() method to pass in the BeanFactory container instance
  6. ApplicationContextAware: If the Bean implements the ApplicationContextAware interface, Spring will call the Bean's setApplicationContext() method to pass in the bean's application context reference.
  7. BeanPostProcessor: If the Bean implements the BeanPostProcessor interface, Spring will call their postProcessBeforeInitialization() method.
  8. InitializingBean: If the Bean implements the InitializingBean interface, Spring will call their afterPropertiesSet() method. Similarly, if the bean declares an initialization method using init-method, this method will also be called
  9. BeanPostProcessor: If the Bean implements the BeanPostProcessor interface, Spring will call their postProcessAfterInitialization() method. 
  10. Lifetime: The Bean is ready to be used, and all Beans will remain in the application context until the application context is destroyed.
  11. @PreDestroy: If there is a method annotated with @PreDestroy; the Spring container will call this method before destroying itself.
  12. Destruction: If the bean implements the DisposableBean interface, Spring will call its destroy() interface method. Similarly, if the bean uses the destroy-method declaration to destroy the method, this method will also be called.

5. Talk about circular dependencies and L3 cache

 Circular dependency, three-level cache to solve the underlying principle of circular dependency, introduction to three-level cache

 Circular dependencies: 

AService{注入BService}
BService{注入AService}

The underlying principle of three-level cache to solve circular dependency: 

 AService creation process:

  • Inference and instantiation: infer the construction method and instantiate the original object of AService;
  • Store in L3 cache: Generate a Lambda expression and put it into L3 cache. Lambda expression parameters include the original object. If this Lambda expression is executed in the future, aop will be executed and an AService proxy object will be generated; singletonFactories.put('AService', ()-> getEarlyBeanReference(beanName, mbd, AService common object)
  • Attribute filling BService: go to the first-level cache and second-level cache to find the BService; if not found, create a BService object. BService creation process:
    • Inference and instantiation: infer the constructor and instantiate the original object of BService;
    • Store in the third-level cache: Generate the Lambda expression of BService based on the original object and put it into the third-level cache. In the future, if this Lambda expression is executed, it will generate an advance proxy object of BService;
    • Attribute filling AService: fill AService proxy object based on reflection to AService attribute;
      • Singleton pool search: First go to the first-level cache (singleton pool) to find AService. If found, it means that AService has gone through a complete life cycle and there is no circular dependency. Beans with a complete life cycle are stored in the first-level cache.
      • Judging circular dependencies: If not found, it means that AService has not gone through a complete life cycle, and it is necessary to judge circular dependencies. The judgment method is to search for AService in creatingSet<>, if found, it means that AService is being created, and a circular dependency occurs.
      • Second-level cache lookup: Find AService from the second-level cache. The second-level cache is earlySingletonObjects, which stores Beans that have not experienced a full life cycle and are ahead of AOP.
      • If the third-level cache finds it, generate an advance proxy object and store it in the second-level cache: if not found, execute the Lambda expression of the third-level cache AService, generate an AService advance proxy object, store it in the second-level cache and return it;
    • Execute the third-level cache: execute the Lambda expression of the third-level cache BService to get the BService proxy object;
    • Stored in the singleton pool : the BService proxy object is placed in the first-level cache;
  • AOP package initialization: execute the postProcessBeforeInitialization() method of BeanPostProcessor; execute the initialization method: Bean rewrites afterPropertiesSet() of the InitializingBean interface; execute the postProcessAfterInitialization() method of BeanPostProcessor
  • Stored in the singleton pool : AService proxy objects are placed in the first-level cache;

L3 cache:

  • singletonObjects (singleton pool): The cached bean objects have gone through a complete life cycle.
  • earlySingletonObjects: Cache beans that have not passed the full life cycle, that is, early proxy objects. When used for circular dependencies, look for the proxy object in advance directly from the second-level cache.
  • singletonFactories: The cache is a Lambda expression, the parameters include the original object, and the execution result is the proxy object in advance. Store the Lambda expression in the third-level cache immediately after the Bean is instantiated. When a circular dependency occurs and the second-level cache cannot find the advance proxy object, the Lambda expression will be obtained and executed, and the generated advance proxy object will be stored in the second-level cache first and then the attributes will be filled.

An example of a Lambda expression stored in singletonFactories:

singletonFactories.put('AService',()-> getEarlyBeanReference(beanName,mbd,AService普通对象);

L3 cache:

  • singletonObjects: caches bean objects that have gone through a complete life cycle.
  • earlySingletonObjects: Cache beans that have not passed the full life cycle. If a bean has a circular dependency, the bean that has not passed the full life cycle will be put into earlySingletonObjects in advance. If the bean needs to go through AOP, then the proxy Put the object into earlySingletonObjects, otherwise put the original object into earlySingletonObjects, but no matter what, the original object represented by the proxy object has not gone through the full life cycle, so we can put it into earlySingletonObjects and we can uniformly think that it has not gone through the full life cycle the bean.
  • singletonFactories: cached is an ObjectFactory, which is a Lambda expression. During the generation process of each bean, after an original object is obtained through instantiation, a Lambda expression will be exposed in advance based on the original object and saved in the third-level cache. This Lambda expression may or may not be used , if the current bean does not have a circular dependency, then this Lambda expression is useless . The current bean is executed normally according to its own life cycle. After execution, the current bean is directly put into singletonObjects. If the current bean finds a cycle during dependency injection Dependency (the bean currently being created is dependent on other beans), get the Lambda expression from the third-level cache, execute the Lambda expression to get an object, and put the obtained object into the second-level cache (if the current bean needs AOP, then execute the lambda expression to get the corresponding proxy object , if you don’t need AOP, you can get an original object directly).

Spring's core code for solving circular dependencies: the first and second level caches cannot find the lambda expression that will execute the third level cache, and put it into the second level cache:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Quick check for existing instance without full singleton lock
	// 单例池存在Bean
	Object singletonObject = this.singletonObjects.get(beanName);
	// 单例池不存在,并且正在创建,去二级缓存中找
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		singletonObject = this.earlySingletonObjects.get(beanName);
		// 二级缓存中不存在并且第二个参数为true(默认为true,放入二级缓存)
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				// Consistent creation of early reference within full singleton lock
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						// 从三级缓存中得到当前Bean对应的信息
						ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
						// 存在这些信息的时候
						if (singletonFactory != null) {
							// 执行三级缓存的lambda表达式,并且赋值给外部的结果
							singletonObject = singletonFactory.getObject();
							// 将执行结果放入二级缓存
							this.earlySingletonObjects.put(beanName, singletonObject);
							// 删除当前数据在三级缓存总的存储值
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	// 返回从单例池,或者早期单例池,或者刚执行完lambda表达式的结果
	return singletonObject;
}

6. Talk about several registration methods of Bean

  • @Configuration and @Bean
  • @Component and @Component
  • @Import:
    • Import a normal class: load the class into the container as a configuration class
    • Import the class that implements the ImportSelector interface: rewrite the selectImports method, return a String[] array, and the class corresponding to each fully qualified name in the array will be injected into the spring container.
    • Import the class that implements the ImportBeanDefinitionRegister interface: rewrite the registerBeanDefinitions() method, and manually register the beanDefinition in the beanDefinitionMap.
  • Implement the FactoryBean interface: rewrite the getObject() method, and register the returned object as a Bean.
  • Implement the BeanDefinitionRegistryPostProcessor interface: manually register the BeanDefinition with the container

7. Talk about the scope of Bean, and the default scope

scoring points

singleton, prototype, request, session, globalSession, custom scope

standard answer

Five scopes: 

Annotate @Scope on the Bean, and set the scope to singleton (default), prototype, request, session, and globalSession.

  • singleton singleton (default): There is only one instance in the Spring container, that is, the Bean exists in the form of a singleton.
  • prototype prototype: Every time a Bean is obtained from the Spring container, the new operation will be performed and a new instance will be returned.
  • request request: Every HTTP request creates a new Bean.
  • session: The same HTTP Session shares a Bean, and different HTTP Sessions use different Beans. The same HTTP Session refers to the connection established between the same client and the same server, including the same client after restarting the same browser.
  • globalSession: The same global Session shares a Bean. The global session stores global public data that has nothing to do with the request, and becomes invalid after the application is restarted. The data can be restored after the persistent disk fails and restarts.

Custom scope:

  1. Create MyScope, implement the Scope interface, and rewrite the get() and remove() methods;
  2. Annotate @Scope("MyScope") on Bean

By default, Bean is a singleton in the Spring container, but we can modify the scope of the Bean through the @Scope annotation . This annotation has five different values, representing five different types of scopes for beans

singleton singleton (default): There is only one instance in the Spring container, that is, the Bean exists in the form of a singleton.

prototype prototype: Every time getBean() is called, the new operation will be performed and a new instance will be returned.

request : A new Bean is created for each HTTP request.

session: The same HTTP Session shares a Bean, and different HTTP Sessions use different Beans.

globalSession: The same global Session shares a Bean, which is generally used in the Portlet environment.

Bonus answer - custom scope:

It is very simple to use Spring annotations to customize the bean scope. You can implement the org.springframework.beans.factory.config.Scope interface and combine it with the @Scope annotation provided in the Spring framework.

Specific steps are as follows:

  1. Implement the Scope interface and override the get() and remove() methods.
  2. Add the @Scope annotation to the implementation class and specify its value as the name of the custom Scope.

The get() method is used to get an object instance with the given name from the current scope. Should return null if the object is not found. The remove() method is used to remove the object instance with the specified name from the current scope. If the object cannot be found, no action.

For example: Suppose you need to implement a MyScope custom scope, you can follow the steps below:

Define the MyScope class, which implements the Scope interface, as follows:

public class MyScope implements Scope {

    private final Map<String, Object> scopedObjects = new HashMap<>();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        if (!scopedObjects.containsKey(name)) {
            scopedObjects.put(name, objectFactory.getObject());
        }
        return scopedObjects.get(name);
    }

    @Override
    public Object remove(String name) {
        return scopedObjects.remove(name);
    }

    // 其他方法省略
}

8. Talk about the difference between BeanFactory and FactoryBean

scoring points

BeanFactory definition, FactoryBean definition

standard answer

BeanFactory is an IOC container. It has some methods to determine whether a Bean exists in the container, whether it is a singleton, etc.

FactoryBean is an interface used to register beans. Override the getObject() method and register the returned object as a Bean.

They are very similar in spelling, one is Factory, which is the IoC container or object factory, and the other is Bean.

BeanFactory:

In Spring, all beans are managed by the BeanFactory (that is, the IoC container) .

FactoryBean:

But for FactoryBean, this bean is not a simple bean, but a factory bean that can generate or modify object generation. Its implementation is similar to the factory pattern and decorator pattern in the design pattern. When the user uses the container, the escape character "&" can be used to obtain the FactoryBean itself, which is used to distinguish between obtaining the object generated by the FactoryBean and obtaining the FactoryBean itself. For example, if alphaObject is a FactoryBean, then use &alphaObject to get the FactoryBean, not the object generated by the FactoryBean alphaObject. Among them, alphaObject is the name specified when defining the Bean.

public class MyFactoryBean implements FactoryBean<MyBean> {
//创建并返回了MyBean的实例
    @Override
    public MyBean getObject() throws Exception {
        // 创建MyBean对象
        return new MyBean();
    }

    @Override
    public Class<?> getObjectType() {
//返回的是MyBean的Class类型
        return MyBean.class;
    }
//指示了MyBean是否为单例模式
    @Override
    public boolean isSingleton() {
        return true;
    }
}

Bonus points back to the -BeanFactory definition method:

- getBean(String name):

The method to obtain the corresponding Bean object in the Spring container, if it exists, return the object

- contnsBean(String name): Whether the object exists in the Spring container

- isSingleton(String name): Whether the beanName is a singleton object

- isPrototype(String name): Determine whether the bean object is a multi-instance object

- isTypeMatch(String name, ResolvableType typeToMatch): Determine whether the bean obtained from the name value matches typeToMath

- getType(String name): Get the Class type of the Bean

- getAliases(String name): Get all the aliases corresponding to name

FactoryBean method:

- T getObject(): returns the instance

- Class getObjectType();: Returns the type of the Bean of the decorated object

- default boolean isSingleton(): Whether the Bean is a singleton

9. Talk about the difference between @Autowired and @Resource annotations

scoring points

Annotation sources, injection methods, @Resource assembly order

The role of both annotations is dependency injection. 

@Autowired:

  • Annotation source: Annotations provided by Spring. The coupling degree is higher than @Resource, because it is a Spring annotation, which depends on Spring's IOC container, and this annotation cannot be recognized after changing the IOC framework.
  • Injection method: Inject by type by default. If there are multiple bean instances of the same type but different names in the IOC container, an error will be reported when the application starts, prompting that only singleton beans can be injected. It can be used with the @Qualifier("Bean name") annotation to achieve injection by name. You can annotate @Primary on the specified bean, and inject it by name first.
  • required attribute: The required attribute is true by default, and the injection of Bean instances is mandatory. After the application starts, if there is no corresponding type of Bean in the IOC container, an error will be reported.

@Resource:

  • Annotation source: Annotations provided by JDK. is the Java standard. The degree of coupling is lower than that of @Autowired. If attribute injection must be used, it is recommended to use @Resource, which has a lower degree of coupling. When using @Autowired attribute injection, IDEA will warn that "property injection is not recommended, constructor injection is recommended", but @Resource will not warn.
  • Injection method: Inject by name by default. That is to say, if neither the name nor the type attribute is specified, it will match according to the variable name first, and then match according to the type. If there is no match, an error will be reported.
  • name attribute: Specify the Bean name and inject it by name.
  • type attribute: specify the Bean type and inject it by type.

Three injection methods:

  • Constructor injection : the most recommended, with the lowest degree of coupling, but it is cumbersome to write and has poor performance;
  • Setter injection : When setter is injected, the object cannot be set to final.
  • Field injection : Not recommended, although the code is concise but highly coupled, the object cannot be set to final, and the class cannot be reflected. If you must use attribute injection, it is recommended to use @Resource, which has a lower degree of coupling.
    • Violation of the Single Responsibility Principle: Because of the attribute-based injection method, it violates the Single Responsibility Principle. Today's business generally uses a lot of dependencies, but having too many dependencies usually means taking on more responsibilities, which obviously violates the principle of single responsibility. And the class is strongly coupled with the dependent container and cannot be used outside the container. 
    • It may cause Spring initialization failure: When the Spring container is initialized, the property is referenced before being injected, resulting in a null pointer exception, which in turn causes the container initialization to fail.

@Qualifier annotation 

    @Autowired
    @Qualifier("bookDao1")
    private BookDao bookDao;

Property injection:

@RestController
public class AppointmentNumberConfigurationController {

    @Autowired
    private AppointmentNumberConfigurationService numberConfigurationService;
}

Setter injection :

@RestController
public class UserController {


    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService= userService;
}

Field injection :

@RestController
public class UserController {

    final UserService userService;
    
    public UserController(UserService userService) {
        this.userService= userService;
    }
}

10. Talk about the two transaction models of Spring

scoring points

Two transaction programming models, @Transactional principle, why @Transactional is not recommended, isolation level, propagation mechanism, declarative transaction failure scenarios

Two transaction models:

  • Programmatic transactions: Inject TransactionTemplate objects, and write transactions in the execute method. You can also use try-catch to send reliable messages and other flexible transaction compensation methods.
  • Declarative transaction: method or class annotation @Transactional. The principle is AOP + exception capture + database transaction, generating proxy objects, automatically opening and submitting/rolling back transactions before and after the execution of business methods, the bottom layer depends on database transactions, if the database storage engine does not support transactions, then Spring transactions cannot be realized. The attributes of @Transactional can set the timeout period, isolation level, propagation mechanism, rollback exception, and non-roll exception of the transaction.

Spring provides a consistent template for transaction management and establishes a unified abstraction of things at a high level.

Spring supports two transactional programming models:

1. Programmatic transactions

Spring provides a TransactionTemplate template, with which we can implement transaction management through programming without paying attention to operations such as resource acquisition, reuse, release, transaction synchronization, and exception handling. Compared with declarative transactions, this method is relatively troublesome, but fortunately it is more flexible, and we can control the scope of transaction management more precisely.

@Service
public class MyService {
    @Autowired
    private TransactionTemplate transactionTemplate;

    public void doSomething() {
        transactionTemplate.execute(new TransactionCallback<Void>() {
            public Void doInTransaction(TransactionStatus status) {
                try {
                    // 业务逻辑代码
                    return null;
                } catch (Exception ex) {
//其实不需要try catch手动回滚,如果代码块里出现异常,TransactionTemplate会自动回滚
                    status.setRollbackOnly();
                    throw ex;
                }
            }
        });
    }
}

2. Declarative transactions

The highlight of Spring transaction management is declarative transaction management, which allows us to specify transaction boundaries and transaction attributes in the IoC configuration by declarative means, and Spring will automatically apply transaction attributes on the specified transaction boundaries. Compared with programmatic transactions, this method is very convenient. You only need to add the @Transactional annotation to the method that needs to do transaction management to declare transaction characteristics.

Declarative transaction management is the highlight of Spring transaction management, which allows us to specify transaction boundaries and transaction attributes in the IoC configuration by declarative means, and Spring will automatically apply transaction attributes on the specified transaction boundaries. Compared with programmatic transactions, this method is very convenient. You only need to add the @Transactional annotation to the method that needs to do transaction management to declare transaction characteristics.

When we add the @Transactional annotation to the business method, Spring will generate a proxy object based on the annotation, and wrap the proxy object into a transaction enhancer, which automatically opens and commits/rolls back the transaction before and after the execution of the business method.

The opening, rollback, and committing of transactions are done by the transaction manager . When we use different database access frameworks, we need to use the corresponding transaction manager. In Spring Boot, when you add the starting dependency of the database access framework, it will be automatically configured, that is, the correct transaction manager will be automatically instantiated.

For declarative transactions, @Transactional is used for annotation. This annotation can be marked on the class or method.

- When it is marked on a class , it means that all public (public) non-static methods of this class will enable transaction functions.

- When it is marked on a method , it means that this method will enable the transaction function.

On the @Transactional annotation, we can use the isolation attribute to declare the isolation level of the transaction , and use the propagation attribute to declare the propagation mechanism of the transaction .

11. Talk about 11 scenarios where declarative transactions fail

The bottom layer of declarative transactions: database transactions + AOP + exception capture. As long as one of them is violated, the transaction will be invalidated.

The database transaction fails, causing the declarative transaction to fail: 

  • Distributed scenario: Spring local transactions must be rollback and commit of the same service and the same database, and if one is not satisfied, the transaction will fail.
  • Multi-threading: Create a thread in the transaction method to access the database, and the two threads are not connected in the same connection, resulting in transaction failure. The same transaction actually refers to the same database connection, and only the same database connection can be committed and rolled back at the same time. If it is in different threads, the database connection obtained must be different, so it is a different transaction.
    • Requirements: Multi-threaded import Excel to the database, it is required that either all imports succeed or all imports fail.
    • Solution: Before creating a multi-thread, first obtain the database connection Connection through SqlSession, set the non-automatic commit connection.setAutoCommit(false), and then submit the connection.commit() if there is no exception in the multi-thread, and roll back the connection if there is an exception. rollback(), whether there is an exception can be judged by AtomicBoolean. Ensure that the database operations under each thread are under one connection, and can be submitted together when submitting, and rolled back together if there is an exception.
  • Storage engines that do not support transactions: For example, MyISAM does not support transactions.

Aop fails, causing declarative transactions to fail: 

  • Access is not public: transaction methods must be public. There is a judgment in the computeTransactionAttribute method of the AbstractFallbackTransactionAttributeSource class. If the target method is not public, TransactionAttribute returns null, that is, transactions are not supported.
  • final method: The final method cannot be rewritten, and the proxy class cannot rewrite this method to add transaction functions.
  • Non-transactional methods in the same category directly call transactional methods: because transactions are based on dynamic proxy objects, this non-transactional method will not generate proxy objects, and this calling transactional methods will not generate proxy objects, so the transaction fails. Non-transactional methods must call transactional methods with proxy objects, that is, inject themselves and then call transactional methods.
    • In addition, the transaction method directly calls this non-transaction method, and the transaction takes effect. It is equivalent to directly moving the code of the called method to the calling method, no matter which of the two methods throws an exception, it will be rolled back.
    • After the transaction method is injected into itself and then the transaction method is called, it will take effect according to the transaction propagation behavior. The default propagation behavior of the called transaction is REQUIRED, that is, joining the current transaction, so the effect is the same as this call.
  • Propagation behavior does not allow to open transactions: only REQUIRED, REQUIRES_NEW, NESTED support new transactions.
  • Not managed by Spring: The class to which it belongs is not a Bean, and there are no annotations such as @Service, @Component, etc.
  • Forget to open a transaction: For example, forget @Transactional.

The exception is caught, causing the declarative transaction to fail: 

  • The exception is caught by other things: the transaction is based on aop+ to catch the exception, if you use try-catch to catch the exception yourself, the transaction will not be rolled back.

12. How to solve the multi-threaded transaction failure problem?

Use JDBC to operate the database and ensure multi-threaded transactions through the same connection: 

  1. Get the Connection object
  2. The Connection object closes automatic submission; 
  3. In multi-threading, the database is operated through this Connection object; if there is an exception, it will be rolled back, and if there is no exception, it will be submitted;
  4. Close the database connection;
public class TransactionDemo {

    private static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase";
    private static final String DB_USER = "username";
    private static final String DB_PASSWORD = "password";

    public static void main(String[] args) {
        Connection connection = null;

        try {
            // 创建数据库连接
            connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

            // 关闭自动提交
            connection.setAutoCommit(false);

            // 创建并启动多个线程执行事务操作
            Thread thread1 = new TransactionThread(connection, "Thread 1");
            Thread thread2 = new TransactionThread(connection, "Thread 2");
            thread1.start();
            thread2.start();

            // 等待线程执行完毕
            thread1.join();
            thread2.join();

            // 提交事务
            connection.commit();
            System.out.println("事务提交成功!");
        } catch (SQLException | InterruptedException e) {
            e.printStackTrace();
            // 发生异常,回滚事务
            try {
                if (connection != null) {
                    connection.rollback();
                    System.out.println("事务回滚成功!");
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            // 关闭数据库连接
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

class TransactionThread extends Thread {

    private Connection connection;
    private String threadName;

    public TransactionThread(Connection connection, String threadName) {
        this.connection = connection;
        this.threadName = threadName;
    }

    @Override
    public void run() {
        try {
            // 1.在每个线程中执行事务操作
            System.out.println(threadName + "开始执行事务操作...");

            // 2.connection执行事务相关的操作

        String sql = "INSERT INTO stu VALUES (..),(..)...";
        //2.1. 获取执行sql的对象 Statement
        Statement stmt = conn.createStatement();
        //2.2. 执行sql
        int count = stmt.executeUpdate(sql);//受影响的行数
        //2.3. 处理结果。可以通过count数量判断异常,为方便测试,下面模拟异常。
        System.out.println(count);

            // 3.模拟发生异常
            if (threadName.equals("Thread 2")) {
                throw new RuntimeException("模拟发生异常");
            }

            System.out.println(threadName + "事务操作执行完毕!");
        } catch (RuntimeException e) {
            e.printStackTrace();
            // 4.发生异常,抛出异常以回滚事务
            throw e;
        }
    }
}

13. Why is it not recommended to use declarative transactions?

Reasons why @Transactional is not recommended:

  • Transaction failure problem: There are 12 failure scenarios for declarative transactions, and if you don’t pay attention, you will step on the pit;
  • Catching exceptions leads to transaction failure: declarative transactions are based on aop. When there are multiple proxy objects, as long as one proxy object catches the exception and rolls back, other proxy objects cannot catch the exception and cannot be rolled back.
  • Transaction granularity problem: Declare the method as a transaction, and there are many read operations in the transaction that do not require the transaction. Putting it in the transaction will make the transaction longer, occupying the database connection time becomes longer and cannot be released, and once the request volume increases, the database will be blocked. wear down.
  • Security and simplicity: Although Spring officially recommends the use of declarative transactions, it is mainly from the perspective of simplicity, and it is recommended to use programmatic transactions from the perspective of security.

14. Talk about the isolation level of Spring transactions

When multiple transactions operate on the same database at the same time, the impact of one transaction reading data that has been committed by other transactions but not persisted in the database. 

  • Read uncommitted: the lowest isolation level, transactions can read data from other uncommitted transactions;
  • Read Committed: Data can only be read after other transactions are committed. Solve dirty reads.
  • Repeatable read (default): Ensure that when the same data is queried multiple times within a transaction, the results are consistent. Solve dirty reads and non-repeatable reads
  • Serialization: The isolation level is the highest, and all operations are serialized during the entire transaction process. Solve dirty reads, non-repeatable reads and phantom reads, but poor performance

Dirty read: read dirty data. The current transaction reads data just changed by another uncommitted transaction.

Non-repeatable read: The data read repeatedly before and after are not the same. The same data is read twice before and after. During this period, the data is changed by other transactions, resulting in different data read before and after.

Phantom reading: The data read before and after are the same, but there are a few more lines or a few less lines, like an illusion. The data sets read before and after the transaction are different, resulting in "phantom" rows. Only serialization can solve the phantom read problem.

Spring's transaction isolation level

Spring's transaction isolation level (Transaction Isolation Level) refers to the impact of a transaction reading data that has been committed by other transactions but not persisted to the database when multiple transactions operate on the same database at the same time. Spring provides five standard transaction isolation levels:

TRANSACTION_READ_UNCOMMITTED (read uncommitted content): the lowest isolation level, transactions can read data from other uncommitted transactions, and problems such as dirty reads, non-repeatable reads, and phantom reads may occur.

TRANSACTION_READ_COMMITTED (read committed content): Ensure that data can only be read after other transactions are committed, which solves the problem of dirty reads, but still has the problems of non-repeatable reads and phantom reads.

TRANSACTION_REPEATABLE_READ (repeatable read): Ensure that when the same data is queried multiple times within a transaction, the results are consistent. Dirty reads and non-repeatable reads are solved, but phantom reads still occur.

TRANSACTION_SERIALIZABLE (serialization): The isolation level is the highest, and all operations are serialized during the entire transaction process, which solves all the above problems, but has a greater impact on performance.

TRANSACTION_DEFAULT (consistent with the database default setting): Use the default isolation level of the database, which depends entirely on the implementation of the database.

Choosing the appropriate isolation level requires consideration of many factors, including the access mode of the application, the importance of data, and the degree of concurrency. By default, Spring uses the database's default isolation level settings. When programming or declarative transaction management, you can specify the required transaction isolation level through the @Transactional annotation or related configuration.

15. Talk about the propagation behavior of Spring transactions

Transaction propagation behavior: When multiple transaction methods call each other, how transactions are propagated between these methods.

  • REQUIRED (default): Join a transaction if there is a current transaction, and open a new transaction if there is no transaction.
  • REQUIRES_NEW: Force a new transaction, regardless of whether there is a current transaction.
  • MANDATORY: Mandatory to join the current transaction, if there is no transaction, an error will be reported. Mandatory is translated as mandatory and obligatory.
  • SUPPORTS: Join the transaction if there is a transaction currently, and execute non-transaction if there is no transaction.
  • NOT_SUPPORTED: Force non-transactional execution, and suspend if there is a transaction.
  • NEVER: Force non-transactional execution, and report an error if there is a transaction.
  • NESTED: If there is a current transaction, the transaction will be nested, and the failure will not affect the outer transaction.

Propagation Behavior of Transactions

Transaction Propagation Behavior (Transaction Propagation Behavior) refers to the relevant rules for obtaining resources, releasing resources, and performing operations between transactions during multiple transaction processing. Spring provides seven standard transaction propagation behaviors:

  1. PROPAGATION_REQUIRED: The default propagation behavior, if there is no current transaction, start a new transaction; if there is an existing transaction, join the transaction.

  2. PROPAGATION_SUPPORTS: Support current transaction. If there is a transaction, use the transaction; if not, non-transactional execution.

  3. PROPAGATION_MANDATORY: Mandatory must exist in the current transaction, otherwise an exception is thrown.

  4. PROPAGATION_REQUIRES_NEW: Regardless of whether there is a current transaction, a new transaction will be opened to execute the current method, and other existing transactions will be suspended.

  5. PROPAGATION_NOT_SUPPORTED: The current method is directly executed in a non-transactional manner. If there is a current transaction, the transaction will be suspended first, and the suspended transaction will be resumed after the current function is executed.

  6. PROPAGATION_NEVER: The current method must be executed in a non-transactional manner, and an exception is thrown if there is a current transaction.

  7. PROPAGATION_NESTED: If the current transaction exists, start a nested transaction and execute each task. If the nested transaction succeeds, it will be submitted together with the current transaction; when the nested transaction fails, only the nested transaction will be rolled back, and the current transaction will continue run.

For all the above propagation behaviors, as long as an exception occurs when forming an operation chain, and the exception is not caught and processed at multiple levels, the entire process will trigger a rollback operation.

Which kind of propagation behavior to choose needs to be fully considered in combination with specific business scenarios, follow the management principle of minimization, and reduce the additional overhead caused by transactions such as lock tables, deadlocks, and timeouts as much as possible on the premise of ensuring data correctness . By default, Spring uses PROPAGATION_REQUIRED as the propagation behavior setting for all methods annotated with @Transactional in the application.

16. What happens if the database goes down during transaction execution?

The database maintains the state before the transaction: if the database is down during the execution of the transaction, the data in the database will be consistent with that before the transaction, and it will not be locked and can be accessed by other transactions, and this database connection will be cleared (JDBC connection and Druid connection pool), the database transaction will be cleared.

Test steps:

0. View the original database data, number of connections, number of transactions

1. The test class prepares and executes a transaction method that blocks for 6000s;

    @Test
    @Transactional(rollbackFor = Exception.class)
    public void testUpdate(){
        update();
 
        try {
            //模拟长事务,方便我们测试系统宕机
            Thread.sleep(6000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

2. Check the database connection, you can find that this connection is being used:

show FULL PROCESSLIST;

3. Check the database transaction and find that this transaction is being executed:

SELECT * FROM information_schema.INNODB_TRX;

4. Kill the process of the business system

taskkill /f /pid 13964

5. Check the connection, transaction, and data again, and find that the connection has been closed, the transaction has ended, and the data has not changed.

17. What happens if the transaction rollback fails?

Analysis: Transaction rollback failure is equivalent to downtime during transaction execution, the database will maintain the state before the transaction, and there will be no problems such as dirty data.

Solution: mainly depends on the business scenario.

  • The user manually retries and submits the business again: sacrifice user experience, but reduce business complexity.
  • Reliable MQ compensation rollback (recommendation): Use try-catch to catch exceptions. If an exception is caught, reliable MQ (confirm+manual ack) will be sent, and consumers will process the entire compensation logic to roll back. Although there is still a risk of downtime during the period from capturing exceptions to sending reliable MQ, sending MQ to the broker is very fast (RabbitMQ millisecond level, rocketMQ subtle level), and there are message persistence and queue persistence.
  • Transaction rollback table: Every step of the rollback business logic is written in the transaction rollback table, and the scheduled tasks continuously scan the status and roll back. For example, "submit form rollback table", the fields are "form ID", "database delete form", "Redis delete form", "ES delete form", "other rollback logic such as unfreezing waybill", each task is executed , modify the field status of that item to "rolled back". The advantage can ensure the absolute success of the transaction rollback, but the disadvantage is that the cost is too high, because every access to the database by a high-concurrency system will greatly affect the performance. After all, the maximum number of connections for a single database is only 150 by default.

Guess you like

Origin blog.csdn.net/qq_40991313/article/details/131207135