Spring常用干货整理(方便记忆)

338 阅读8分钟

灵感和参考

作者:舟晚梦星辰
链接:juejin.cn/post/717493…

作者: 苏三说技术[原稿]
链接:juejin.cn/post/714508…

一、常用标签(有整理错误,请留言,我这边修正,方便未来复看)

www.processon.com/view/link/6…

二、拦截器

1.拦截器、过滤器、监听器的执行顺序

监听器 > 过滤器 > 拦截器 > servlet执行 > 拦截器 > 过滤器 > 监听器

2.多个拦截器的执行顺序(两个)

(1)当俩个拦截器都实现放行操作时,顺序为preHandle 1,preHandle 2,postHandle 2,postHandle 1,afterCompletion 2,afterCompletion 1;

(2)当第一个拦截器preHandle返回false,也就是对其进行拦截时,第二个拦截器是完全不执行的,第一个拦截器只执行preHandle部分;

(3)当第一个拦截器preHandle返回true,第二个拦截器preHandle返回false,顺序为preHandle 1,preHandle 2 ,afterCompletion 1。

3.多个过滤器的执行顺序

web服务器根据Filter在web.xml中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法,在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第二个filter,如果没有,则调用目标资源。

4.多个监听器的执行顺序

一个webServlet里面若有多个监听器的话,顺序是按照加载的顺序来加载和注册的这些servlet监听器的。

spring中拦截器主要分两种,一个是HandlerInterceptor,一个是MethodInterceptor。

5.spring拦截器

HandlerInterceptor

是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。

spring mvc拦截器的顶层接口是:HandlerInterceptor,包含三个方法:

  • preHandle 目标方法执行前执行
  • postHandle 目标方法执行后执行
  • afterCompletion 请求完成时执行

为了方便我们一般情况会用HandlerInterceptor接口的实现类HandlerInterceptorAdapter类。

假如有防重复提交、跨域、权限认证、操作日志、统计等场景,可以使用该拦截器。

为了方便我们一般情况会用HandlerInterceptor接口的实现类HandlerInterceptor 不推荐后面实现类 HandlerInterceptorAdapter类。 第二步,将该拦截器注入WebMvcConfigurer不推荐后面实现类WebMvcConfigurerAdapter

注:Spring5.0废弃了.........Adapter ,6.0已经转为内部类了.

MethodInterceptor

方法拦截,记录方法的log日志,在controller请求上面使用标签用过。 可参考下面回忆: blog.csdn.net/WX5991/arti…

三、通过容器的方式获取对象

在我们日常开发中,如果你不是从controller开始,而是new对象,那么该对象内的(@Autowired或@Resource)标签并不会生效。

1.BeanFactoryAware接口

package com.nice.core.util;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.stereotype.Component;

@Component
public class BeanContext implements BeanFactoryAware {

    private static BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
    public static <T> T getBean(String name) throws BeansException {
        return (T)beanFactory.getBean(name);
    }

    public static <T> T getBean(Class<T> clz) throws BeansException {
        return (T)beanFactory.getBean(clz);
    }

}

引用:

Person person = BeanContext.().getBean(Person.class);

2.ApplicationContextAware接口(我常用这个)

package com.nice.core.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class BeanContext implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanContext.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T)applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clz) throws BeansException {
        return (T)applicationContext.getBean(clz);
    }
}

引用:

Person person = BeanContext.getApplicationContext().getBean(Person.class);

3.ApplicationListener接口

package com.nice.core.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class BeanContext implements ApplicationListener<ContextRefreshedEvent> {

    private static ApplicationContext applicationContext;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        applicationContext = event.getApplicationContext();
    }

    public static <T> T getBean(String name) throws BeansException {
        return (T)applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clz) throws BeansException {
        return (T)applicationContext.getBean(clz);
    }
}

引用:(这个与上面引用的区别自己看)

Person person = BeanContext.().getBean(Person.class);

四、异常处理(非常好用)

package cn.nice.core.response;

import cn.hutool.core.util.StrUtil;
import cn.leap.common.response.ResponseBody;
import cn.leap.common.response.ResponseCode;
import cn.leap.common.singlelock.core.LockException;
import cn.leap.common.utils.JacksonUtil;
import com.alibaba.excel.exception.ExcelAnalysisException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.sql.SQLException;

@Slf4j
@RestControllerAdvice
public class ResponseAdvisor implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    /**
     * Controller层返回数据前处理函数
     */
    @Override
    public Object beforeBodyWrite(Object o,
                                  MethodParameter methodParameter,
                                  MediaType mediaType,
                                  Class<? extends HttpMessageConverter<?>> aClass,
                                  ServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse) {
        ResponseBody body = new ResponseBody();
        body.setRequestId(MDC.get("traceId"));
        if (!(o instanceof ResponseBody)) {
            body.init(o);
            // String类型的返回值比较特殊
            if (o instanceof String)
                o = JacksonUtil.toString(body);
            else
                o = body;
        }
        return o;
    }

    /**
     * Controller层发生异常后进行包装处理
     */
    @ExceptionHandler({Exception.class})
    public Object globalExceptionHandler(Exception e) {
        String mes = e.getMessage() == null ? " 空指针异常 " : e.getMessage();
        log.error(mes, e);
        ResponseBody body = new ResponseBody();
        body.setRequestId(MDC.get("traceId"));
        if (e instanceof NoHandlerFoundException) {
            body.init(ResponseCode.FAIL, StrUtil.format("请【截全图】联系,客服人员<NoHandlerFoundException> {}", mes));
        } else {
            body.init(ResponseCode.FAIL, mes);
        }
        return body;
    }
}

只需在handleException方法中处理异常情况,业务接口中可以放心使用,不再需要捕获异常,一般公司技术老大会把常用异常统一封装好的。

五、类型转换器

spring目前支持3种类型转换器:

  • Converter<S,T>:将 S 类型对象转为 T 类型对象
  • ConverterFactory<S, R>:将 S 类型对象转为 R 类型及子类对象
  • GenericConverter:它支持多个source和目标类型的转化,同时还提供了source和目标类型的上下文,这个上下文能让你实现基于属性上的注解或信息来进行类型转换。

不说了,百度. 个人感觉这个实际使用有点鸡肋。

项目中常用:

1.String 转日期

2.日期 转 String

3.list转map

4.map转对象

5.对象copy

6.集合对象copy

7 json -> Gson之toJson和fromJson方法

六、项目启动

springboot提供了:

- CommandLineRunner

- ApplicationRunner

应用

系统参数获取、初始化数据等

执行顺序

  • @Order/@Priority 值相同时,ApplicationRunner 优先于 CommandLineRunner。
  • @Order/@Priority 值和继承的接口类型都相同时,按照注入容器的顺序(应该是按照类的名称)

springboot 完全初始化完毕后,执行

@Component
public class ServerDispatcher implements CommandLineRunner {
    @Override
    public void run(String... args){
        // 逻辑代码
    }
}

@Component
public class ServerDispatcher implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args){
        // 逻辑代码
    }
}

执行顺序

BeanFactoryPostProcessor ---> 普通Bean构造方法 ---> 设置依赖或属性 Constructor >> @Autowired >> @PostConstruct > InitializingBean > ApplicationRunner

七、监听机制

@Component
public class ServerDispatcher implements ApplicationListener<ContextRefreshedEvent> {
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 逻辑代码
    }
}

执行时机

在 IOC 容器的启动过程,当所有的 bean 都已经处理完成之后,spring 的 ioc 容器会有一个发布 ContextRefreshedEvent 事件的动作。

注意事项

系统会存在两个容器,一个是 root application context , 另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)

这种情况下,就会造成 onApplicationEvent 方法被执行两次。为了避免上面提到的问题,我们可以只在 root application context 初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理

加入判断即可:

if (event.getApplicationContext().getParent() == null)

@Component
public class ServerDispatcher implements ApplicationListener<ContextRefreshedEvent> {
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 当 spring 容器初始化完成后就会执行该方法
        if (event.getApplicationContext().getParent() == null) { 
            //逻辑代码
        }
    }
}

监听事件类型

ContextRefreshedEvent:ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。此处的初始化是指:所有的 Bean 被成功装载,后处理 Bean 被检测并激活,所有 Singleton Bean 被预实例化,ApplicationContext 容器已就绪可用。

ContextStartedEvent:当使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。

ContextStoppedEvent:当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。

ContextClosedEvent:当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。

RequestHandledEvent:这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。

八、 修改BeanDefinition

Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修 改其中bean定义的某些属性,为bean定义增加其他信息等。

参考地址: zhuanlan.zhihu.com/p/522265861

@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());
    }
}

九、初始化Bean前后

BeanPostProcessor接口。

该接口目前有两个方法:

  • postProcessBeforeInitialization 该在初始化方法之前调用。
  • postProcessAfterInitialization 该方法在初始化方法之后调用。
@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;
    }
}

其实,我们经常使用的注解,比如:@Autowired、@Value、@Resource、@PostConstruct等,是通过AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor实现的 BeanPostProcessor等。

十、初始化方法

1.使用@PostConstruct注解

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

2.实现InitializingBean接口

@Service
public class BService implements InitializingBean {

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

十一、关闭容器前销毁

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

通常情况下,我们会同时实现InitializingBean和DisposableBean接口,重写初始化方法和销毁方法。

切面思想很重要,除非不使用当前生态架构。