11 eye-catching tricks in Spring

foreword

When we talk about spring, the first thing that may come to mind is IOC(inversion of control) and AOP(aspect-oriented programming).

That's right, they are the cornerstone of spring, and thanks to their excellent design, spring can stand out from many excellent frameworks.

In addition, did we find it in the process of using spring 扩展能力非常强. Due to the existence of this advantage, spring has a strong tolerance, so that many third-party applications can easily fall into the embrace of spring. For example: rocketmq, mybatis, redis, etc.

Let’s chat with you today, the 11 most commonly used extension points in Spring.

1. Custom interceptor

Compared with the root spring interceptor of the spring mvc interceptor, it can obtain HttpServletRequestand HttpServletResponsewait for web object instances.

The top-level interface of the spring mvc interceptor is: HandlerInterceptor, which contains three methods:

  • preHandle executes before the target method is executed
  • postHandle Executed after the target method is executed
  • afterCompletion is executed when the request is completed

For convenience, we generally use HandlerInterceptorthe implementation class of the interface HandlerInterceptorAdapter.

This interceptor can be used if there are scenarios for authorization authentication, logs, and statistics.

In the first step, the inherited HandlerInterceptorAdapterclass defines the interceptor:

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("===权限校验===");
        return true;
    }
}

The second step is to register the interceptor to 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());
    }
}

In the third step, spring mvc can automatically intercept the interface and verify the authority through the interceptor when requesting the interface.

2. Get the Spring container object

In our daily development, we often need to get beans from the Spring container, but do you know how to get the Spring container object?

2.1 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");
    }
}

Implement BeanFactoryAwarethe interface, and then rewrite setBeanFactorythe method, you can get the spring container object from the method.

2.2 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");
    }
}

Implement ApplicationContextAwarethe interface, and then rewrite setApplicationContextthe method, and you can also get the spring container object from this method.

2.3 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");
    }
}

3. Global exception handling

In the past, when we were developing the interface, if an exception occurred, in order to give the user a more friendly prompt, for example:

@RequestMapping("/test")
@RestController
public class TestController {
    
    

    @GetMapping("/add")
    public String add() {
    
    
        int a = 10 / 0;
        return "成功";
    }
}

If you do not do any processing to request the add interface result, an error will be reported directly:

what? Can the user see the error message directly?

This kind of interaction gives users a very poor experience. In order to solve this problem, we usually catch exceptions in the interface:

@GetMapping("/add")
public String add() {
    
    
    String result = "成功";
    try {
    
    
        int a = 10 / 0;
    } catch (Exception e) {
    
    
        result = "数据异常";
    }
    return result;
}

After the interface is modified, when an exception occurs, it will prompt: "data exception", which is more user-friendly.

It looks pretty good, but there is a problem. . .

It's okay if it's just one interface, but if there are hundreds or thousands of interfaces in the project, do you have to add exception catching code?

The answer is no, and this is 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 "数据异常";
        }
        if (e instanceof Exception) {
    
    
            return "服务器内部异常";
        }
        retur nnull;
    }
}

You only need handleExceptionto handle exceptions in the method, and you can use them safely in the business interface, and you no longer need to catch exceptions (someone handles them uniformly). Really cool.

4. Type Converter

Spring currently supports 3 type converters:

  • Converter<S,T>: Convert an object of type S to an object of type T
  • ConverterFactory<S, R>: Convert S-type objects to R-type and subclass objects
  • GenericConverter: It supports the conversion of multiple source and target types, and also provides the context of source and target types. This context allows you to perform type conversion based on annotations or information on attributes.

These three types of converters are used in different scenarios, let's take Converter<S,T>为an example. If: In the entity object receiving parameters in the interface, there is a field whose type is Date, but the actual parameter passed is a string type: 2021-01-03 10:20:15, how to deal with it?

The first step is to define an entity User:

@Data
public class User {
    
    

    private Long id;
    private String name;
    private Date registerDate;
}

The second step is to implement Converterthe 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;
    }
}

The third step is to 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());
    }
}

The fourth step is to call the interface

@RequestMapping("/user")
@RestController
public class UserController {
    
    

    @RequestMapping("/save")
    public String save(@RequestBody User user) {
    
    
        return "success";
    }
}

When requesting the interface, the registerDate field in the User object will be automatically converted to the Date type.

5. Import configuration

Sometimes we need to introduce other classes in a certain configuration class, and the imported classes are also added to the spring container. At this time, annotations can be used @Importto complete this function.

If you look at its source code, you will find that the imported classes support three different types.

But I think it is best to explain the configuration classes of ordinary classes and @Configuration annotations separately, so four different types are listed:

5.1 Common classes

This introduction method is the simplest, and the imported class will be instantiated as a bean object.

public class A {
    
    
}

@Import(A.class)
@Configuration
public class TestConfiguration {
    
    
}

Introduce class A through @Importannotations, and spring can automatically instantiate A objects, and then @Autowiredinject them through annotations where they need to be used:

@Autowired
private A a;

Is it surprising? @BeanBean can be instantiated without annotation .

5.2 Configuration class

This introduction method is the most complicated, because @Configurationannotations also support a variety of combined annotations, such as:

  • @Import
  • @ImportResource
  • @PropertySource etc.
public class A {
    
    
}

public class B {
    
    
}

@Import(B.class)
@Configuration
public class AConfiguration {
    
    

    @Bean
    public A a() {
    
    
        return new A();
    }
}

@Import(AConfiguration.class)
@Configuration
public class TestConfiguration {
    
    
}

The configuration class annotated with @Configuration is imported through the @Import annotation, and the classes introduced by the annotations related to the configuration class @Import, @ImportResource, , etc. will be recursively introduced all at once.@PropertySource

5.3 ImportSelector

This import method needs to implement ImportSelectorthe interface:

public class AImportSelector implements ImportSelector {
    
    

private static final String CLASS_NAME = "com.sue.cache.service.test13.A";
    
 public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    
        return new String[]{
    
    CLASS_NAME};
    }
}

@Import(AImportSelector.class)
@Configuration
public class TestConfiguration {
    
    
}

The advantage of this method is that selectImportsthe 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 implement ImportBeanDefinitionRegistrarthe interface:

public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(A.class);
        registry.registerBeanDefinition("a", rootBeanDefinition);
    }
}

@Import(AImportBeanDefinitionRegistrar.class)
@Configuration
public class TestConfiguration {
    
    
}

This method is the most flexible. The container registration object can be registerBeanDefinitionsobtained in the method , and the creation and registration of BeanDefinition can be manually controlled.BeanDefinitionRegistry

6. When the project starts

Sometimes we need to customize some additional functions when the project starts, 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 achieve the above requirements.

Their usage is quite simple, take ApplicationRunnerthe interface as an example:

@Component
public class TestRunner implements ApplicationRunner {
    
    

    @Autowired
    private LoadDataService loadDataService;

    public void run(ApplicationArguments args) throws Exception {
    
    
        loadDataService.load();
    }
}

Implement ApplicationRunnerthe interface, rewrite runthe method, and realize your own customized requirements in this method.

If there are multiple classes in the project that implement the ApplicationRunner interface, how to specify their execution order?

The answer is to use @Order(n)annotations, the smaller the value of n, the earlier it will be executed. Of course, the order can also @Prioritybe specified through annotations.

7. Modify BeanDefinition

Before instantiating the Bean object, Spring IOC needs to read the relevant properties of the Bean, save it in BeanDefinitionthe object, and then instantiate the Bean object through the BeanDefinition object.

What if you want to modify the properties in the BeanDefinition object?

Answer: We can implement BeanFactoryPostProcessorinterfaces.

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    
    
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    
    
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder.addPropertyValue("id", 123);
        beanDefinitionBuilder.addPropertyValue("name", "苏三说技术");
        defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());
    }
}

In the postProcessBeanFactory method, you can get the related object of BeanDefinition and modify the properties of the object.

8. Before and after initializing the Bean

Sometimes, you want to implement some logic of your own before and after the bean is initialized.

At this time, it can be realized: BeanPostProcessorinterface.

The interface currently has two methods:

  • postProcessBeforeInitialization should be called before the initialization method.
  • postProcessAfterInitialization This method is called after the initialization method.

For example:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    
    

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    
        if (bean instanceof User) {
    
    
            ((User) bean).setUserName("苏三说技术");
        }
        return bean;
    }
}

If there is a User object in spring, set its userName to: Su San said technology.

In fact, the annotations we often use, such as: @Autowired, @Value, @Resource, @PostConstruct, etc., are implemented through AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor.

9. Initialization method

Currently, there are many ways to initialize beans in spring:

  1. Use the @PostConstruct annotation
  2. Implement the InitializingBean interface

9.1 Using the @PostConstruct annotation

@Service
public class AService {
    
    
    @PostConstruct
    public void init() {
    
    
        System.out.println("===初始化===");
    }
}

Add annotations to the methods that need to be initialized @PostConstruct, so that they have the ability to initialize.

9.2 Implement the InitializingBean interface

@Service
public class BService implements InitializingBean {
    
    

    @Override
    public void afterPropertiesSet() throws Exception {
    
    
        System.out.println("===初始化===");
    }
}

Implement InitializingBeanthe interface and rewrite afterPropertiesSetthe method, in which the initialization function can be completed.

10. Before closing the container

Sometimes, we need to do some extra work before closing the spring container, such as: closing resource files, etc.

At this point you can implement DisposableBeanthe interface and override its destroymethods:

@Service
public class DService implements InitializingBean, DisposableBean {
    
    
 
    @Override
    public void destroy() throws Exception {
    
    
        System.out.println("DisposableBean destroy");
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
    
    
        System.out.println("InitializingBean afterPropertiesSet");
    }
}

In this way, before the spring container is destroyed, the destroy method will be called to do some extra work.

Usually, we will implement the InitializingBean and DisposableBean interfaces at the same time, and rewrite the initialization method and destruction method.

11. Custom scope

We all know that spring supports Scopeonly two types by default:

  • singleton singleton, each bean obtained from the spring container is the same object.
  • There are multiple instances of prototype, and the beans obtained from the spring container are different objects each time.

spring web has extended Scope again, adding:

  • RequestScope The beans obtained from the spring container in the same request are all the same object.
  • SessionScope The beans obtained from the spring container by the same session are all the same object.

Even so, some scenes just didn't meet our requirements.

For example, the beans we want to get from the spring container in the same thread are all the same object, what should we do?

This requires a custom Scope.

The first step is to 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;
    }
}

The second step is to 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());
    }
}

The third step uses the newly defined Scope:

@Scope("threadLocalScope")
@Service
public class CService {
    
    
    public void add() {
    
    
    }
}

One last word (please pay attention, don't prostitute me for nothing)

If this article is helpful or inspiring to you, please scan and send the QR code to pay attention. Your support is the biggest motivation for me to keep writing.

Ask for one-click triple link: like, forward, and watch.

Pay attention to the official account: [Su San said technology], reply in the official account: interviews, code artifacts, development manuals, time management have awesome fan benefits, and reply: join the group, you can communicate and learn from many seniors of BAT manufacturers .

Guess you like

Origin blog.csdn.net/lisu061714112/article/details/127162957