最近愈来愈多的读者承认个人文章,仍是件挺让人高兴的事情。有些读者私信我说但愿后面多分享spring方面的文章,这样可以在实际工做中派上用场。正好我对spring源码有过必定的研究,并结合我这几年实际的工做经验,把spring中我认为不错的知识点总结一下,但愿对您有所帮助。git
`@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");` `}` `}` `复制代码`
实现BeanFactoryAware
接口,而后重写setBeanFactory
方法,就能从该方法中获取到spring容器对象。github
`@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");` `}` `}` `复制代码`
实现ApplicationContextAware
接口,而后重写setApplicationContext
方法,也能从该方法中获取到spring容器对象。web
`@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");` `}` `}` `复制代码`
实现ApplicationListener
接口,须要注意的是该接口接收的泛型是ContextRefreshedEvent
类,而后重写onApplicationEvent
方法,也能从该方法中获取到spring容器对象。面试
此外,不得不提一下Aware
接口,它实际上是一个空接口,里面不包含任何方法。spring
它表示已感知的意思,经过这类接口能够获取指定对象,好比:缓存
Aware
接口是很经常使用的功能,目前包含以下功能:springboot
spring中支持3种初始化bean的方法:服务器
第一种方法太古老了,如今用的人很少,具体用法就不介绍了。mybatis
`@Service` `public class AService {` `@PostConstruct` `public void init() {` `System.out.println("===初始化===");` `}` `}` `复制代码`
在须要初始化的方法上增长@PostConstruct
注解,这样就有初始化的能力。架构
`@Service` `public class BService implements InitializingBean {` `@Override` `public void afterPropertiesSet() throws Exception {` `System.out.println("===初始化===");` `}` `}` `复制代码`
实现InitializingBean
接口,重写afterPropertiesSet
方法,该方法中能够完成初始化功能。
这里顺便抛出一个有趣的问题:init-method
、PostConstruct
和 InitializingBean
的执行顺序是什么样的?
决定他们调用顺序的关键代码在AbstractAutowireCapableBeanFactory
类的initializeBean
方法中。
这段代码中会先调用BeanPostProcessor的postProcessBeforeInitialization
方法,而PostConstruct
是经过InitDestroyAnnotationBeanPostProcessor
实现的,它就是一个BeanPostProcessor
,因此PostConstruct
先执行。
而invokeInitMethods
方法中的代码:
决定了先调用InitializingBean
,再调用init-method
。
因此得出结论,他们的调用顺序是:
咱们都知道spring默认支持的Scope
只有两种:
spring web又对Scope进行了扩展,增长了:
即使如此,有些场景仍是没法知足咱们的要求。
好比,咱们想在同一个线程中从spring容器获取到的bean都是同一个对象,该怎么办?
这就须要自定义Scope了。
第一步实现Scope接口:
`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;` `}` `}` `复制代码`
第二步将新定义的Scope注入到spring容器中:
`@Component` `public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {` `@Override` `public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {` `beanFactory.registerScope("threadLocalScope", new ThreadLocalScope());` `}` `}` `复制代码`
第三步使用新定义的Scope:
`@Scope("threadLocalScope")` `@Service` `public class CService {` `public void add() {` `}` `}` `复制代码`
提及FactoryBean
就不得不提BeanFactory
,由于面试官老喜欢问它们的区别。
若是你看过spring源码,会发现它有70多个地方在用FactoryBean接口。
上面这张图足以说明该接口的重要性,请勿忽略它好吗?
特别提一句:mybatis
的SqlSessionFactory
对象就是经过SqlSessionFactoryBean
类建立的。
咱们一块儿定义本身的FactoryBean:
`@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;` `}` `}` `复制代码`
获取FactoryBean实例对象:
`@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);` `}` `}` `复制代码`
getBean("myFactoryBean");
获取的是MyFactoryBeanService
类中getObject
方法返回的对象,getBean("&myFactoryBean");
获取的才是MyFactoryBean
对象。spring目前支持3中类型转换器:
这3种类型转换器使用的场景不同,咱们以Converter<S,T>
为例。假如:接口中接收参数的实体对象中,有个字段的类型是Date,可是实际传参的是字符串类型:2021-01-03 10:20:15,要如何处理呢?
第一步,定义一个实体User:
`@Data` `public class User {` `private Long id;` `private String name;` `private Date registerDate;` `}` `复制代码`
第二步,实现Converter接口:
`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;` `}` `}` `复制代码`
第三步,将新定义的类型转换器注入到spring容器中:
`@Configuration` `public class WebConfig extends WebMvcConfigurerAdapter {` `@Override` `public void addFormatters(FormatterRegistry registry) {` `registry.addConverter(new DateConverter());` `}` `}` `复制代码`
第四步,调用接口
`@RequestMapping("/user")` `@RestController` `public class UserController {` `@RequestMapping("/save")` `public String save(@RequestBody User user) {` `return "success";` `}` `}` `复制代码`
请求接口时User对象中registerDate字段会被自动转换成Date类型。
spring mvc拦截器根spring拦截器相比,它里面可以获取HttpServletRequest
和HttpServletResponse
等web对象实例。
spring mvc拦截器的顶层接口是:HandlerInterceptor
,包含三个方法:
为了方便咱们通常状况会用HandlerInterceptor
接口的实现类HandlerInterceptorAdapter
类。
假若有权限认证、日志、统计的场景,可使用该拦截器。
第一步,继承HandlerInterceptorAdapter类定义拦截器:
`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;` `}` `}` `复制代码`
第二步,将该拦截器注册到spring容器:
`@Configuration` `public class WebAuthConfig extends WebMvcConfigurerAdapter {` `@Bean` `public AuthInterceptor getAuthInterceptor() {` `return new AuthInterceptor();` `}` `@Override` `public void addInterceptors(InterceptorRegistry registry) {` `registry.addInterceptor(getAuthInterceptor());` `}` `}` `复制代码`
第三步,在请求接口时spring mvc经过该拦截器,可以自动拦截该接口,而且校验权限。
该拦截器其实相对来讲,比较简单,能够在DispatcherServlet
类的doDispatch
方法中看到调用过程:
顺便说一句,这里只讲了spring mvc的拦截器,并无讲spring的拦截器,是由于我有点小私心,后面就会知道。
不知道你有没有用过Enable
开头的注解,好比:EnableAsync、EnableCaching、EnableAspectJAutoProxy等,这类注解就像开关同样,只要在@Configuration
定义的配置类上加上这类注解,就能开启相关的功能。
是否是很酷?
让咱们一块儿实现一个本身的开关:
第一步,定义一个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("记录请求日志");` `chain.doFilter(request, response);` `System.out.println("记录响应日志");` `}` `@Override` `public void destroy() {` `}` `}` `复制代码`
第二步,注册LogFilter:
`@ConditionalOnWebApplication` `public class LogFilterWebConfig {` `@Bean` `public LogFilter timeFilter() {` `return new LogFilter();` `}` `}` `复制代码`
注意,这里用了@ConditionalOnWebApplication
注解,没有直接使用@Configuration
注解。
第三步,定义开关@EnableLog注解:
`@Target(ElementType.TYPE)` `@Retention(RetentionPolicy.RUNTIME)` `@Documented` `@Import(LogFilterWebConfig.class)` `public @interface EnableLog {` `}` `复制代码`
第四步,只需在springboot
启动类加上@EnableLog
注解便可开启LogFilter记录请求和响应日志的功能。
咱们使用RestTemplate
调用远程接口时,有时须要在header
中传递信息,好比:traceId,source等,便于在查询日志时可以串联一次完整的请求链路,快速定位问题。
这种业务场景就能经过ClientHttpRequestInterceptor
接口实现,具体作法以下:
第一步,实现ClientHttpRequestInterceptor接口:
`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);` `}` `}` `复制代码`
第二步,定义配置类:
`@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();` `}` `}` `复制代码`
其中MdcUtil实际上是利用MDC工具在ThreadLocal
中存储和获取traceId
`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);` `}` `}` `复制代码`
固然,这个例子中没有演示MdcUtil类的add方法具体调的地方,咱们能够在filter中执行接口方法以前,生成traceId,调用MdcUtil类的add方法添加到MDC中,而后在同一个请求的其余地方就能经过MdcUtil类的get方法获取到该traceId。
之前咱们在开发接口时,若是出现异常,为了给用户一个更友好的提示,例如:
`@RequestMapping("/test")` `@RestController` `public class TestController {` `@GetMapping("/add")` `public String add() {` `int a = 10 / 0;` `return "成功";` `}` `}` `复制代码`
若是不作任何处理请求add接口结果直接报错:
what?用户能直接看到错误信息?
这种交互方式给用户的体验很是差,为了解决这个问题,咱们一般会在接口中捕获异常:
`@GetMapping("/add")` `public String add() {` `String result = "成功";` `try {` `int a = 10 / 0;` `} catch (Exception e) {` `result = "数据异常";` `}` `return result;` `}` `复制代码`
接口改造后,出现异常时会提示:“数据异常”,对用户来讲更友好。
看起来挺不错的,可是有问题。。。
若是只是一个接口还好,可是若是项目中有成百上千个接口,都要加上异常捕获代码吗?
答案是否认的,这时全局异常处理就派上用场了: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;` `}` `}` `复制代码`
只需在handleException
方法中处理异常状况,业务接口中能够放心使用,再也不须要捕获异常(有人统一处理了)。真是爽歪歪。
之前咱们在使用异步功能时,一般状况下有三种方式:
让咱们一块儿回顾一下:
`public class MyThread extends Thread {` `@Override` `public void run() {` `System.out.println("===call MyThread===");` `}` `public static void main(String[] args) {` `new MyThread().start();` `}` `}` `复制代码`
`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();` `}` `}` `复制代码`
`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();` `}` `}` `}` `复制代码`
这三种实现异步的方法不能说很差,可是spring已经帮咱们抽取了一些公共的地方,咱们无需再继承Thread
类或实现Runable
接口,它都搞定了。
如何spring异步功能呢?
第一步,springboot项目启动类上加@EnableAsync
注解。
`@EnableAsync` `@SpringBootApplication` `public class Application {` `public static void main(String[] args) {` `new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);` `}` `}` `复制代码`
第二步,在须要使用异步的方法上加上@Async注解:
`@Service` `public class PersonService {` `@Async` `public String get() {` `System.out.println("===add==");` `return "data";` `}` `}` `复制代码`
而后在使用的地方调用一下:personService.get();就拥有了异步功能,是否是很神奇。
默认状况下,spring会为咱们的异步方法建立一个线程去执行,若是该方法被调用次数很是多的话,须要建立大量的线程,会致使资源浪费。
这时,咱们能够定义一个线程池,异步方法将会被自动提交到线程池中执行。
`@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;` `}` `}` `复制代码`
spring异步的核心方法:
根据返回值不一样,处理状况也不太同样,具体分为以下状况:
spring cache架构图:
它目前支持多种缓存:
咱们在这里以caffeine为例,它是spring官方推荐的。
第一步,引入caffeine的相关jar包
`<dependency>` `<groupId>org.springframework.boot</groupId>` `<artifactId>spring-boot-starter-cache</artifactId>` `</dependency>` `<dependency>` `<groupId>com.github.ben-manes.caffeine</groupId>` `<artifactId>caffeine</artifactId>` `<version>2.6.0</version>` `</dependency>` `复制代码`
第二步,配置CacheManager
,开启EnableCaching
`@Configuration` `@EnableCaching` `public class CacheConfig {` `@Bean` `public CacheManager cacheManager(){` `CaffeineCacheManager cacheManager = new CaffeineCacheManager();` `//Caffeine配置` `Caffeine<Object, Object> caffeine = Caffeine.newBuilder()` `//最后一次写入后通过固定时间过时` `.expireAfterWrite(10, TimeUnit.SECONDS)` `//缓存的最大条数` `.maximumSize(1000);` `cacheManager.setCaffeine(caffeine);` `return cacheManager;` `}` `}` `复制代码`
第三步,使用Cacheable
注解获取数据
`@Service` `public class CategoryService {` `//category是缓存名称,#type是具体的key,可支持el表达式` `@Cacheable(value = "category", key = "#type")` `public CategoryModel getCategory(Integer type) {` `return getCategoryByType(type);` `}` `private CategoryModel getCategoryByType(Integer type) {` `System.out.println("根据不一样的type:" + type + "获取不一样的分类数据");` `CategoryModel categoryModel = new CategoryModel();` `categoryModel.setId(1L);` `categoryModel.setParentId(0L);` `categoryModel.setName("电器");` `categoryModel.setLevel(3);` `return categoryModel;` `}` `}` `复制代码`
调用categoryService.getCategory()方法时,先从caffine
缓存中获取数据,若是可以获取到数据则直接返回该数据,不会进入方法体。若是不能获取到数据,则直接方法体中的代码获取到数据,而后放到caffine缓存中。
若是这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写做最大的动力。
求一键三连:点赞、转发、在看。源于:juejin.cn/post/6931630572720619534