As it happens, I have done some research on Spring source code, and combined with my practical work experience in these years, I think the spring knowledge points summed up, I hope to help you.
Welcome to pay attention to my B station account
B station account
If the content helps you, welcome everyone to like, favorites + attention
Learning exchange group
Communication group
How do I get spring container objects
1. Implement BeanFactoryAware interface
@Service public class PersonService implements BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } public void add() { Person person = (Person) beanFactory.getBean("person"); }}Copy the code
Implement the BeanFactoryAware interface and then override the setBeanFactory method to get the Spring container object from that method.
2. Implement the ApplicationContextAware interface
@Service
public class PersonService2 implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void add() {
Person person = (Person) applicationContext.getBean("person");
}
}
Copy the code
Implement the ApplicationContextAware interface and then override the setApplicationContext method to get the Spring container object from that method as well.
3. Implement ApplicationListener interface
@Service public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> { private ApplicationContext applicationContext; @Override public void onApplicationEvent(ContextRefreshedEvent event) { applicationContext = event.getApplicationContext(); } public void add() { Person person = (Person) applicationContext.getBean("person"); }}Copy the code
Implement the ApplicationListener interface. Note that the interface receives the ContextRefreshedEvent class, and then overwrites the onApplicationEvent method to get the Spring container object from that method as well.
In addition, I have to mention the Aware interface, which is an empty interface that contains no methods.
It represents a perceived meaning, through which an object can be obtained, for example:
-
Get the BeanFactory from BeanFactoryAware
-
Get the ApplicationContext via ApplicationContextAware
-
Get BeanName and so on from BeanNameAware
The Aware interface is a very common feature and currently contains the following features:
How to initialize the bean
There are three ways to initialize beans supported in Spring:
-
Specifies the init-method method in XML
-
Use the @postconstruct annotation
-
Implement the InitializingBean interface
The first method is too old, not many people use it now, the specific usage will not be introduced.
1. Use @postconstruct annotations
@service public class AService {@postconstruct public void init() {system.out.println ("=== initialization ==="); }}Copy the code
Add @postConstruct annotations to the methods that need to be initialized, so you have the ability to initialize.
2. Implement InitializingBean interface
@Service public class BService implements InitializingBean { @Override public void afterPropertiesSet() throws Exception {system.out. println("=== initialization ==="); }}Copy the code
Implement the InitializingBean interface and override the afterPropertiesSet method, which performs initialization.
An interesting question arises here: What is the order in which init-Method, PostConstruct, and InitializingBeans are executed?
Determine their calls to order the key code in AbstractAutowireCapableBeanFactory initializeBean method of a class.
This code will first invoke the BeanPostProcessor postProcessBeforeInitialization method, and is achieved by InitDestroyAnnotationBeanPostProcessor PostConstruct, It’s a BeanPostProcessor, so PostConstruct executes first.
And the code in invokeInitMethods:
The decision is to call InitializingBean first, followed by init-method.
So they conclude that the order of their calls is:
Customize your own Scope
We all know that Spring supports only two scopes by default:
-
Singleton, the same object is retrieved each time from the Spring container.
-
Each time a bean is retrieved from the Spring container, it is a different object.
Spring Web also extends Scope to add:
-
RequestScope gets the same bean from the Spring container for the same request.
-
SessionScope All beans that a session obtains from the Spring container are the same object.
Even so, some scenarios just didn’t cut it.
For example, what if all the beans we want to get from the Spring container in the same thread are the same object?
This is where you need to customize the Scope.
Step 1 Implement the Scope interface:
public class ThreadLocalScope implements Scope { private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal(); @Override public Object get(String name, ObjectFactory<? > objectFactory) { Object value = THREAD_LOCAL_SCOPE.get(); if (value ! = null) { return value; } Object object = objectFactory.getObject(); THREAD_LOCAL_SCOPE.set(object); return object; } @Override public Object remove(String name) { THREAD_LOCAL_SCOPE.remove(); return null; } @Override public void registerDestructionCallback(String name, Runnable callback) { } @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return null; }}Copy the code
Step 2 inject the newly defined Scope into the Spring container:
@Component public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerScope("threadLocalScope", new ThreadLocalScope()); }}Copy the code
Step 3 Use the newly defined Scope:
@Scope("threadLocalScope")
@Service
public class CService {
public void add() {
}
}
Copy the code
Don’t say factoryBeans are useless
You can’t talk about a FactoryBean without mentioning a BeanFactory because interviewers like to ask the difference.
-
BeanFactory: The top-level interface of the Spring container that manages the bean factory.
-
Factorybeans: Not ordinary factory beans, factoryBeans hide the details of instantiating complex beans to the benefit of upper-layer applications.
If you look at spring’s source code, it uses the FactoryBean interface in over 70 places.
The diagram above illustrates the importance of this interface. Do not ignore it, ok?
The SqlSessionFactory object of Mybatis is created using the SqlSessionFactoryBean class.
Let’s define our FactoryBean together:
@Component
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
String data1 = buildData1();
String data2 = buildData2();
return buildData3(data1, data2);
}
private String buildData1() {
return "data1";
}
private String buildData2() {
return "data2";
}
private String buildData3(String data1, String data2) {
return data1 + data2;
}
@Override
public Class<?> getObjectType() {
return null;
}
}
Copy the code
Get the FactoryBean instance object:
@Service public class MyFactoryBeanService implements BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } public void test() { Object myFactoryBean = beanFactory.getBean("myFactoryBean"); System.out.println(myFactoryBean); Object myFactoryBean1 = beanFactory.getBean("&myFactoryBean"); System.out.println(myFactoryBean1); }}Copy the code
-
getBean(“myFactoryBean”); Gets the object returned by the getObject method of MyFactoryBeanService,
-
getBean(“&myFactoryBean”); You get the MyFactoryBean object.
Easy to customize type conversion
Spring currently supports three types of converters:
-
Converter<S,T> : Convert an S type object to a T type object
-
ConverterFactory<S, R> : Converts objects of type S to objects of type R and subclasses
-
GenericConverter: It supports conversion of multiple source and target types. It also provides a context for source and target types. This context allows you to convert based on annotations or information on attributes.
The three types of converters use different scenarios. Let’s take Converter
,t>
as an example. 2021-01-03 10:20:15 if the entity object receiving the parameter has a field of type Date, but the actual parameter is a string of type 2021-01-03 10:20:15, how to handle this?
First, define an entity User:
@Data
public class User {
private Long id;
private String name;
private Date registerDate;
}
Copy the code
Step 2, implement the Converter interface:
public class DateConverter implements Converter<String, Date> { private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public Date convert(String source) { if (source ! = null && !" ".equals(source)) { try { simpleDateFormat.parse(source); } catch (ParseException e) { e.printStackTrace(); } } return null; }}Copy the code
Third, inject the newly defined type converter into the Spring container:
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new DateConverter()); }}Copy the code
Step 4, invoke the interface
@RequestMapping("/user") @RestController public class UserController { @RequestMapping("/save") public String save(@RequestBody User user) { return "success"; }}Copy the code
The registerDate field in the User object is automatically converted to Date when the interface is requested.
Six Spring MVC interceptor, used well
The Spring MVC interceptor compares to the Spring interceptor, which can fetch web object instances such as HttpServletRequest and HttpServletResponse.
The top interface of the Spring MVC interceptor is: HandlerInterceptor, which contains three methods:
-
PreHandle Executes before the target method is executed
-
PostHandle target method executes after execution
-
AfterCompletion is executed when the request is complete
In order to facilitate we usually use HandlerInterceptor interface implementation class HandlerInterceptorAdapter class.
You can use this interceptor if you have permission authentication, logging, and statistics scenarios.
The first step, inherit the interceptor HandlerInterceptorAdapter class definition:
public class AuthInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestUrl = request.getRequestURI(); if (checkAuth(requestUrl)) { return true; } return false; } private Boolean checkAuth(String requestUrl) {system.out.println ("=== permission check ==="); return true; }}Copy the code
Second, register the interceptor with the Spring container:
@Configuration public class WebAuthConfig extends WebMvcConfigurerAdapter { @Bean public AuthInterceptor getAuthInterceptor() { return new AuthInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()); }}Copy the code
Third, spring MVC can automatically intercept the interface and verify permissions through the interceptor when requesting the interface.
The interceptor is actually relatively simple and can be seen in the doDispatch method of the DispatcherServlet class:
By the way, I’m only talking about spring MVC interceptors, not spring interceptors, because I’m a little selfish, as I’ll see later.
The Enable switch smells good
I don’t know if you’ve used comments that start with Enable, such as: EnableAsync, EnableCaching, EnableAspectJAutoProxy, etc. These annotations are like switches. You only need to add these annotations to the Configuration class defined by @Configuration to enable related functions.
Isn’t that cool?
Let’s implement a switch of our own:
First, define a LogFilter:
public class LogFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {system.out. println(" Record request log "); chain.doFilter(request, response); System.out.println(" Log response "); } @Override public void destroy() { } }Copy the code
Step 2, register LogFilter:
@ConditionalOnWebApplication public class LogFilterWebConfig { @Bean public LogFilter timeFilter() { return new LogFilter(); }}Copy the code
Note that here in @ ConditionalOnWebApplication annotations, not directly use @ the Configuration notes.
Step 3: Define the switch @enablelog
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LogFilterWebConfig.class)
public @interface EnableLog {
}
Copy the code
In the fourth step, add @enablelog annotation to the SpringBoot startup class to enable LogFilter to record request and response logs.
Eight RestTemplate interceptor spring
When the RestTemplate is used to invoke the remote interface, information, such as traceId and source, needs to be transmitted in the header. In this way, a complete request link can be connected during log query to quickly locate problems.
This business scenario can through ClientHttpRequestInterceptor interface implementation and the specific practices are as follows:
The first step, realize ClientHttpRequestInterceptor interface:
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { request.getHeaders().set("traceId", MdcUtil.get()); return execution.execute(request, body); }}Copy the code
Step 2: Define the configuration class:
@Configuration public class RestTemplateConfiguration { @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Collections.singletonList(restTemplateInterceptor())); return restTemplate; } @Bean public RestTemplateInterceptor restTemplateInterceptor() { return new RestTemplateInterceptor(); }}Copy the code
MdcUtil is actually used to store and obtain traceId in ThreadLocal using MDC tools
public class MdcUtil { private static final String TRACE_ID = "TRACE_ID"; public static String get() { return MDC.get(TRACE_ID); } public static void add(String value) { MDC.put(TRACE_ID, value); }}Copy the code
The MdcUtil add method can be added to MDC by calling the MdcUtil add method before the filter interface method is executed. The traceId can then be obtained elsewhere in the same request through the GET method of the MdcUtil class.
Unified exception handling
In the past, when we were developing the interface, if there was an exception, in order to give the user a more friendly hint, for example:
@RequestMapping("/test") @RestController public class TestController { @GetMapping("/add") public String add() { int a = 10/0; Return C. }}Copy the code
If you do not do any processing request add interface results directly error:
What? Can the user see the error message directly?
This kind of interaction gives the user a very poor experience. To solve this problem, we usually catch exceptions in the interface:
@getMapping ("/add") public String add() {String result = "success "; try { int a = 10 / 0; } catch (Exception e) {result = ""; } return result; }Copy the code
After the interface is modified, a message “Data exception” is displayed when an exception occurs, which is more user-friendly.
It looks good, but there’s a problem…
That’s fine if it’s just one interface, but if you have hundreds or thousands of interfaces in your project, do you need exception catching code?
The answer is no, where global exception handling comes in handy: RestControllerAdvice.
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public String HandleException (Exception e) {if (e instanceof ArithmeticException) {return "data Exception "; } if (e instanceof Exception) {return "server internal Exception "; } retur nnull; }}Copy the code
You only need to handle the exception in the handleException method, and you can use it safely in the business interface. You no longer need to catch the exception. This is awesome.
Ten asynchrony can be just as elegant
In the past, we used asynchronous functionality in one of three ways:
-
Thread class inheritance
-
Implement the Runable interface
-
Using thread pools
Let’s take a look back:
-
Thread class inheritance
public class MyThread extends Thread {
@Override public void run() { System.out.println("===call MyThread==="); } public static void main(String[] args) { new MyThread().start(); } Copy the code
}
-
Implement the Runable interface
public class MyWork implements Runnable { @Override public void run() { System.out.println(“===call MyWork===”); }
public static void main(String[] args) { new Thread(new MyWork()).start(); } Copy the code
}
-
Using thread pools
public class MyThreadPool {
private static ExecutorService executorService = new ThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200)); static class Work implements Runnable { @Override public void run() { System.out.println("===call work==="); } } public static void main(String[] args) { try { executorService.submit(new MyThreadPool.Work()); } finally { executorService.shutdown(); }}Copy the code
}
These three ways to implement asynchrony are not bad, but Spring has already extracted some common ground for us. We don’t need to inherit Thread classes or implement the Runable interface, it’s all done.
How about spring asynchronous functionality?
The first step is to annotate @enableAsync to the SpringBoot project startup class.
@EnableAsync @SpringBootApplication public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}Copy the code
Second, annotate the @async method that requires asynchrony:
@Service public class PersonService { @Async public String get() { System.out.println("===add=="); return "data"; }}Copy the code
Then call personService.get() where it is used; You have asynchrony, isn’t it amazing?
By default, Spring creates a thread for our asynchronous method to execute. If the method is called too many times, a large number of threads need to be created, resulting in a waste of resources.
At this point, we can define a thread pool to which asynchronous methods will be automatically submitted for execution.
@Configuration public class ThreadPoolConfig { @Value("${thread.pool.corePoolSize:5}") private int corePoolSize; @Value("${thread.pool.maxPoolSize:10}") private int maxPoolSize; @Value("${thread.pool.queueCapacity:200}") private int queueCapacity; @Value("${thread.pool.keepAliveSeconds:30}") private int keepAliveSeconds; @Value("${thread.pool.threadNamePrefix:ASYNC_}") private String threadNamePrefix; @Bean public Executor MessageExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(keepAliveSeconds); executor.setThreadNamePrefix(threadNamePrefix); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; }}Copy the code
The core approach to Spring asynchrony:
The processing varies according to the returned value, which can be divided into the following situations:
Eleven heard cache was good, but I didn’t think it was
Spring Cache architecture diagram:
It currently supports multiple caches:
We’ll take Caffeine, which is officially recommended by Spring, as an example.
The first step is to introduce caffeine’s jar package
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> The < version > server < / version > < / dependency >Copy the code
Second, configure CacheManager and enable EnableCaching
@Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager(){ CaffeineCacheManager cacheManager = new CaffeineCacheManager(); <Object, Object> Caffeine = Caffeine. NewBuilder ().expireAfterWrite(10, Timeunit.seconds) // Maximum number of caches. maximumSize(1000); cacheManager.setCaffeine(caffeine); return cacheManager; }}Copy the code
Third, use the Cacheable annotation to retrieve the data
@service public class CategoryService {//category is the cache name,#type is the key, @cacheable (value = "category", key = "#type") public CategoryModel getCategory(Integer type) { return getCategoryByType(type); } private CategoryModel getCategoryByType(Integer type) {system.out.println (" get categorydata according to different types :" + type + "); CategoryModel categoryModel = new CategoryModel(); categoryModel.setId(1L); categoryModel.setParentId(0L); CategoryModel. Elegantly-named setName (" electrical appliances "); categoryModel.setLevel(3); return categoryModel; }}Copy the code
Call categoryService. GetCategory () method, to get the data from the caffine cache first, if you can get to the data is returned to the data directly, will not enter the method body. If the data cannot be retrieved, the code in the method body directly retrieves the data and places it in the Caffine cache.
I often work overtime recently, so I really don’t have much time to write articles