Preface
During the process of using spring, have we found that it has strong expansion capabilities? Due to the existence of this advantage, spring is highly inclusive, so many third-party applications or frameworks can be easily invested in the arms of spring. Today we will mainly learn about the 11 commonly used expansion points in Spring. How many have you used?
1. Type converter
If there is a field type Date in the entity object receiving parameters in the interface, but the actual passed parameter is the string type: 2022-12-15 10:20:15, how to deal with it?
Spring provides an extension point, type converterType Converter
, divided into 3 categories:
-
Converter<S,T>
: Convert an object of type S to an object of type T -
ConverterFactory<S, R>
: Convert an S-type object to an R-type or its subclass object -
GenericConverter
: It supports conversions of multiple source and target types, and also provides context for source and target types. This context allows you to perform type conversion based on comments or attribute information.
If you still don’t understand, let’s give an example.
- Define a user object
@Data public class User { private Long id; private String name; private Date registerDate; }
- accomplish
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 { (source); } catch (ParseException e) { (); } } return null; } }
- Inject a newly defined type converter into a Spring container
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { (new DateConverter()); } }
- Call interface test
@RequestMapping("/user") @RestController public class UserController { @RequestMapping("/save") public String save(@RequestBody User user) { return "success"; } }
When requesting an interface, the date string passed in by the front end will be automatically converted to Date type.
2. Get container beans
In our daily development, we often need to get beans from Spring containers, but do you know how to get Spring container objects?
2.1 BeanFactoryAware
@Service public class PersonService implements BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { = beanFactory; } public void add() { Person person = (Person) ("person"); } }
Implement the BeanFactoryAware interface, and then override the setBeanFactory method, you can get the spring container object from the method.
2.2 ApplicationContextAware
@Service public class PersonService2 implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { = applicationContext; } public void add() { Person person = (Person) ("person"); } }
accomplishApplicationContextAware
Interface, then rewritesetApplicationContext
Method, you can also obtain spring container objects through this method.
2.3 ApplicationListener
@Service public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> { private ApplicationContext applicationContext; @Override public void onApplicationEvent(ContextRefreshedEvent event) { applicationContext = (); } public void add() { Person person = (Person) ("person"); } }
3. Global exception handling
In the past, when we were developing the interface, if there were exceptions, we should give users more friendly prompts, such as:
@RequestMapping("/test") @RestController public class TestController { @GetMapping("/add") public String add() { int a = 10 / 0; return "su"; } }
If you do not do any processing to the result of adding an interface to the request, an error will be reported directly:
Can users see the error message directly?
This kind of interaction brings a very poor experience to the user. 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 = "error"; } return result; }
After the interface is modified, when an exception occurs, it will prompt: "Data exception", which is more user-friendly.
It looks good, but there is a problem.
It would be fine if it was just one interface, but if there were hundreds or thousands of interfaces in the project, would it still have to add exception capture code?
The answer is no, this is where global exception handling comes in handy:RestControllerAdvice
。
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler() public String handleException(Exception e) { if (e instanceof ArithmeticException) { return "data error"; } if (e instanceof Exception) { return "service error"; } retur null; } }
Only the exception is handled in the methodhandleException
, it can be used with peace of mind in the business interface, and no longer needs to catch exceptions (unified and personal handling).
4. Custom interceptor
Spring MVC interceptor, which can be obtainedHttpServletRequest
andHttpServletResponse
etc. web object instance.
The top-level interface of Spring MVC interceptor isHandlerInterceptor
, it contains three methods:
-
preHandle
Execute before the target method is executed - Execute the target method after executing
postHandle
-
afterCompletion
Execute when the request is completed
For convenience, we generally inheritHandlerInterceptorAdapter
, it implementsHandlerInterceptor
。
If there are authorization authentication, logging, statistics and other scenarios, you can use this interceptor, let’s demonstrate it.
- Write a class inheritance
HandlerInterceptorAdapter
:
public class AuthInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestUrl = (); if (checkAuth(requestUrl)) { return true; } return false; } private boolean checkAuth(String requestUrl) { return true; } }
- Register the interceptor into the spring container
@Configuration public class WebAuthConfig extends WebMvcConfigurerAdapter { @Bean public AuthInterceptor getAuthInterceptor() { return new AuthInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { (new AuthInterceptor()); } }
- Spring MVC can automatically intercept the interface when requesting an interface and verify permissions through the interceptor.
5. Import configuration
Sometimes we need to introduce other classes into a configuration class, and the introduced classes are also added to the Spring container. You can use annotations at this time@Import
To complete this function.
If you look at its source code, you will find that the imported class supports three different types.
But I think it's better to configure the normal class and@Configuration
Annotations are explained separately, so four different types are listed:
5.1 General Class
This method of introduction is the easiest, the introduced class will be instantiated into a bean object.
public class A { } @Import() @Configuration public class TestConfiguration { }
pass@Import
Annotation introduces class A, spring can automatically instantiate A objects, and then use annotations where they need to be used.@Autowired
injection:
@Autowired private A a;
5.2 Configuration class
This introduction method is the most complex, because @Configuration supports also support multiple combination annotations, such as:
@Import
@ImportResource
@PropertySource
public class A { } public class B { } @Import() @Configuration public class AConfiguration { @Bean public A a() { return new A(); } } @Import() @Configuration public class TestConfiguration { }
@Configuration
The annotated configuration class passes@Import
Annotation import, configuration class@Import
、@ImportResource
Classes introduced by related annotations will be introduced recursively at once.@PropertySource
The attributes that are located.
5.3 ImportSelector
This import method needs to be implementedImportSelector
interface
public class AImportSelector implements ImportSelector { private static final String CLASS_NAME = "."; public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{CLASS_NAME}; } } @Import() @Configuration public class TestConfiguration { }
The benefit of this approach isselectImports
The method returns an array, which means that multiple classes can be introduced at the same time, which is very convenient.
5.4 ImportBeanDefinitionRegistrar
This import method needs to be implementedImportBeanDefinitionRegistrar
interface:
public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(); ("a", rootBeanDefinition); } } @Import() @Configuration public class TestConfiguration { }
This method is the most flexible. The container registration object can beregisterBeanDefinitions
Get it from the method, can be created manuallyBeanDefinition
Register toBeanDefinitionRegistry
kind.
6. When the project starts
Sometimes we need to customize some extra functions when the project is started, such as loading some system parameters, completing initialization, warming up the local cache, etc. What should we do?
The good news is that SpringBoot provides:
CommandLineRunner
ApplicationRunner
These two interfaces help us realize the above requirements.
Their usage is simple,ApplicationRunner
Interface as an example:
@Component public class TestRunner implements ApplicationRunner { @Autowired private LoadDataService loadDataService; public void run(ApplicationArguments args) throws Exception { (); } }
accomplishApplicationRunner
Interface, rewriterun
Method, implement your custom requirements in this method.
If there are multiple classes in the projectApplicationRunner
Interfaces, how to specify their execution order?
The answer is to use @Order(n) annotation, the smaller the value of n, the earlier it executes. Of course, the order can also be passed@Priority
Annotation is specified.
7. Modify BeanDefinition
Before instantiating the Bean object,Spring IOC
Need to read the relevant properties of the bean and save them inBeanDefinition
In the object, then passBeanDefinition
Object InstantiationBean
Object.
What if you want to modify the properties in the BeanDefinition object?
Answer: We can achieveBeanFactoryPostProcessor
Interface.
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory; BeanDefinitionBuilder beanDefinitionBuilder = (); ("id", 123); ("name", "Tom"); ("user", ()); } }
existpostProcessBeanFactory
In the method, you can obtainBeanDefinition
related objects, modify the properties of the object.
8. Initialize Beans before and after
Sometimes you want to implement some of your own logic before and after bean initialization.
This is the case:BeanPostProcessor
interface.
There are currently two methods for this interface:
-
postProcessBeforeInitialization
: It should be called before initializing the method. -
postProcessAfterInitialization
: This method is called after initializing the method.
@Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof User) { ((User) bean).setUserName("Tom"); } return bean; } }
We use it frequently@Autowired
、@Value
、@Resource
、@PostConstruct
All the annotations are passedAutowiredAnnotationBeanPostProcessor
andCommonAnnotationBeanPostProcessor
To achieve it.
9. Initialization method
Currently there are many ways to initialize beans in Spring:
- use
@PostConstruct
annotation - accomplish
InitializingBean
interface
9.1 Use@PostConstruct
@Service public class AService { @PostConstruct public void init() { ("===init==="); } }
Add annotations to methods that need to be initialized@PostConstruct
, make it execute when bean is initialized.
9.2 Implementing the initialization interfaceInitializingBean
@Service public class BService implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { ("===init==="); } }
accomplishInitializingBean
Interface, rewriteafterPropertiesSet
Method, in which the initialization function can be completed.
10. Before closing Spring container
Sometimes, we need to do some extra work before closing the spring container, such as closing the resource file.
You can do it nowDisposableBean
Interface and rewrite itsdestroy
method.
@Service public class DService implements InitializingBean, DisposableBean { @Override public void destroy() throws Exception { ("DisposableBean destroy"); } @Override public void afterPropertiesSet() throws Exception { ("InitializingBean afterPropertiesSet"); } }
This way, before the spring container is destroyed, it will be calleddestroy
Method to do some extra work.
Usually we will implement it at the same timeInitializingBean
andDisposableBean
Interface, override the initialization method and the destruction method.
11. Customize the scope of the bean
We all know that spring core only supports two types by defaultScope
:
-
Singleton
Singleton, each bean obtained from the spring container is the same object. -
prototype
Multiple instances, each time the beans obtained from the spring container are different objects.
Spring Web has expanded Scope again, adding
-
RequestScope
: The beans retrieved from the spring container in the same request are the same object. -
SessionScope
: The beans retrieved from the spring container of the same session are the same object.
Despite this, some scenarios do not meet our requirements.
For example, we need to go fromspring
Getted in the containerbean
What should I do if I am all the same object?
Answer: This requires a custom range.
- accomplish
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 = (); 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; } }
- Inject the newly defined Scope into the Spring container
@Component public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ("threadLocalScope", new ThreadLocalScope()); } }
- Use the newly defined Scope
@Scope("threadLocalScope") @Service public class CService { public void add() { } }
Summarize
This article summarizes 11 commonly used extension points in Spring. You can inject your desired logic at each stage of bean creation, initialization and destruction. There are also extension points such as Spring MVC-related interceptors, which I hope will be helpful to everyone.
This is the end of this article about the 11 most commonly used extension points in Spring. For more relevant contents of Spring's commonly used extension points, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!