要想自己定制HTTP消息转换器必须先知道SpringBoot默认是怎么做HTTP消息转换的。
先来看我们平常工作中开发一个简单的get请求:
那么当前端请求我们的API的时候,我们是如何将网络传输的字节流序列化成我们想要的对象呢?做完业务处理后又是如何返回数据呢?
HttpMessageConverter
在SpringBoot框架下,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。
打开任何一个集成了Spring-web功能的SpringBoot项目,都可以找到这个HttpMessageConvert:
可以看到他是一个接口类,和他在一个包下面有很多实现类,感兴趣的同学可以debug跟进去把这块源码搞透,这个过程中大家还可以学习到很多关于序列化的知识。
源码解析:怎么知道SpringBoot中的默认convert
在项目的启动类Application.class上有一个@SpringBootApplication注解,点进去会看到他是一系列注解的包含,其中有一个@EnableAutoConfiguration注解,继续点进去查看:
发现引入了一个类:AutoConfigurationImportSelector类,继续跟进去有一个方法selectImports():
继续点击进入loadMetadata这个方法:
到这一步相信大家就清晰了他是怎么加载的了:
继续走下去就知道如何加载消息的默认convert,这里就不和大家说答案,希望大家自己可以跟进去看看,收获是不一样的~
定制自己的convert
知道了消息转换器的原理以及SpringBoot中如何加载的,我们就知道如何去定制自己的消息转换器了,先实现自己的一个convert,继承AbstractHttpMessageConverter:
然后定义一个WebConfig类继承WebMvcConfigurer,把我们自定义的这个convert加进去:
实际工作中,大家可能很多时候并不需要自己去实现convert,用默认的消息转换器就可以了,除非大家有自己特殊的需求考虑,否则不建议非得自己去实现这个convert。
以上就是关于SpringBoot中如何定制消息转换器的回答了,不足之处欢迎大家评论交流,共同学习。
我是【java架构设计】,如果我的回答对您有帮助,欢迎转发点赞,我将持续为您提供Java领域优质内容!
如何系统的学习Spring?正确的阅读源码姿势?
如何系统的学习Spring,以我15年J2EE教学和开发经验来说,首先要清楚Spring的概念和特性,以及在JavaEE框架中起到的作用。在此基础上再开展学习。(欢迎转发)
(1) Spring概念
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架
目的:解决企业应用开发的复杂性
功能:使用基本的JavaBean代替EJB,确保开发的简单性、可测试性和松耦合性;同时提供更多的企业应用功能
范围: Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益
(2)Spring特性
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。不管是SSH,还是SSM,都是由Spring通过IOC和AOP来对对象和对象组装关系进行统一管理。所以学好Spring,就是要学好IOC和AOP。看代码,看Spring源码,都是以这两者为核心。
轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类和接口。
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
面向切面——Spring提供了面向切面编程(AOP)的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如权限、日志和事务管理等)进行内聚性的开发。应用对象只实现它们应该完成的业务功能,不考虑其它的系统级关注点,例如日志或事务支持。
容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,可以配置每个bean如何被创建(bean可以创建一个单独的实例或者每次需要时都生成一个新的实例),以及它们是如何相互关联的。
框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
(3)Spring带来了复杂的J2EE框架开发的春天。
Spring具有如下优点:
低侵入式设计,代码的污染度低;
独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere;
Spring的IoC容器降低了业务对象的耦合性;
Spring的AOP支持允许将一些通用的功能(权限、事务、日志等)等集中处理,提升了复用性,同时让开发者集中精力于业务功能的处理实现;
Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问;
Spring是高度开放的,开发者可以自由选择Spring框架的部分或全部。
(4)目前最流行的Java EE开发框架是SSM,这时就需要以Spring为核心配置与管理工具,掌握spring和spring-mvc进行整合,Spring和mybatis进行整合与配置的方法。才能从容进行基于SSM的Java EE项目的开发。
(5)SpringBoot
目前很火的SpringBoot也是以Spring为基础,它是主要面向web服务,也就是微服务开发的。这个又是另外一个话题了。先学好Spring没错的。
刘嵩
2020.5.19
我是一名大一学生,自学了springboot并搭建了微服务,现在我打算先从底层学起,应该学什么比较好?
能在大一就可以搭建起SpringBoot,那说明你还是具有一定的编程基础的。题主所说的想从底层学起,说明题主可能只是按照SpringBoot教程跑通了简单的SpringBoot Demo,这离成为一名优秀的Java工程师还有很长的一段距离。那要想成为一名优秀的Java工程师,我们还需要学习那些”底层“知识呢?
1、工具
工具分为两个部分,开发工具和项目构建工具。开发工具IDE帮助我们编译、运行、调试、分析、测试代码等等,我比较喜欢IntelliJ IDEA。构建工具用来构建和部署项目,例如maven、gradle和ant。Ant已经没落了,很少有java项目中使用,Maven使用的比较多,但是Maven基于xml语法比较死板,我比较喜欢gradle,推荐大家使用gradle。
2、JDK API
JDK API对于开发人员来说非常重要,这是很大的一部分内容。包括Java Collections框架,Java Concurrency,Java IO和Java 8 API等核心领域。
- 2.1 Java集合框架
这是每个Java开发人员都应该学习的最重要的Java API之一。该API提供了Java中标准数据结构的实现,例如链表,集合,堆栈,队列,哈希表,优先级队列等。
我们对于ArrayList、HashMap、HashSet、LinkedHashSet、TreeSet等框架必须要精通掌握,并知道它们的内部实现逻辑。例如, ArrayList是一个可以增长的动态数组,HashMap 是哈希表的标准实现,可以用于存储键值对。同样,HashSet是一个不允许重复元素的set实现。
- 2.2 java并发
多线程和并发是一个合格的java工程师必须掌握的。我们不仅应该深入了解诸如Thread、Runnable对象锁定和同步之类的基本概念,而且还应该熟悉诸如死锁,乐观锁,condition以及如何使用它们。同时也应该了解像Java5中及以后的版本,例如CyclicBarrier、CountDownLatch、Phaser、CompleteableFuture、Futures等等特性以及如何在Java中执行异步操作。
- 2.3 Java io
Java IO 和NIO API平常开发可能用的比较少,但是对于File、InputStream、OutputStream、Reader以及Writer这些核心API我们要精通掌握。同时如果我们需要编写一个基于套接字的的程序,对于ByteBuffer、FileChannel、Selector也必须掌握。
- 2.4 Java 8新特性
Java8 改变了我们以往的编码和编码方式,以前需要写10行代码才能完成的功能,现在可能只需要几行。Lambda表达式、Stream API、Optional类和新的DateTime API,这些我们都应该熟练掌握。
3、框架
Java生态中有丰富的框架可以供开发者使用。对于主流的框架我们要有一定的了解。例如:Spring、SpringMVC、SpringBoot、Hibernate、Log4j、Mybatis、JUnit等。
- 3.1 Spring Framework
如果您想成为优秀的Java开发人员,强烈建议首先学习Spring Framework。这是最流行的Java框架之一。Spring Framework使开发人员能够编写干净的代码,通过依赖注入和控制反转等功能可以更轻松地测试和维护代码。它还具有用于大多数日常任务的丰富API。
- 3.2 Mybatis
早期ORM框架比较火的是Hibernate。但是Hibernate限制太多,特别是在复杂业务场景下无能为力。Mybatis是目前最流行的ORM框架。支持灵活的sql、存储过程以及高级映射。MyBatis 可以使用简单的XML或注解来配置和映射原生类型、接口和 Java 的 POJO为数据库中的记录。
- 3.3 SpringBoot
SpringBoot应该是目前最火的MVC框架了,SpringBoot将程序员从繁重的XML配置中解放了出来。在没有SpringBoot的时代,我们编写一个后端的web应用,需要大量的xml配置。SpringBoot的出现使创建基于Spring的Java应用程序变得非常容易。你只需要2分钟就可以出创建一个应用并将它跑起来,这极大的提升了程序员的工作效率,使得程序员可以更专注业务代码实现。
4、测试
测试是任何Java开发人员的一项基本技能,尤其是单元测试,集成测试和自动化测试。至少,每个Java开发人员都应该熟悉JUnit和Mockito,这是两种最受欢迎的单元测试和模拟库。如果您知道这两个并知道如何使用它们来有效地创建单元测试,那你将是一个更好的Java开发人员。还存在更高级的库,例如用于业务驱动测试的Cucumber,用于集成测试的Robot Framework。对于模拟库,开发人员可以选择PowerMock,Mockito和EasyMock等几种选择,但我强烈建议学习Mockito,因为它是一个很棒的库,并且许多Java开发人员和公司都在使用它。它正逐渐成为在Java中创建模拟对象的标准库。
5、常用的库
Java的真正功能在于其丰富的开源库生态系统。你会发现库在Java中几乎可以完成所有工作,从日志记录到机器学习,从发送HTTP请求到解析JSON等等。
除此之外,Java还幸运地拥有诸如Apache Commons和Google Guava之类的实用程序库。这两个库有效地补充了JDK库。
最后
如果你已经掌握了这其中一半以上的技术,那恭喜你,你算得上以为优秀的java工程师。如果没有,那好好学习这些技术,它就伴随这你以后职业生涯。当然计算机原理、数据结构、数据库理论等课程将是你大二、大三的专业课,这些课程必须好好学习,深入掌握。
该如何学习spring源码以及解析bean定义的注册?
!我将从以下几点介绍源码及Spring是怎样解析Bean定义并注册的
目录:
-
学习源码的重要性?
-
学习Spring源码需要基础吗?
-
怎样把Spring源码在本地运行?
-
Bean定义的加载过程
-
bean定义加载的流程图
-
总结
学习源码的重要性?
(1) 可以提升技术功底,Spring源码也沉淀了很多年,有非常多的精华所在,不管我们什么水平,通过不断的阅读源码,能对我们的技术有很大的提升,并且工作中遇到类似问题的时候,可以借鉴源码中是怎么处理的。
(2) 深度的掌握技术框架:源码看多了,对于新的框架的学习和掌握都是很快的,看下框架的demo,就知道底层是怎么实现的。
比如,学习了Spring 中的AOP,就知道底层是用了JDK的动态代理。然后我们学习mybatis的时候,就在想Mybatis 为什么Service可以直接嗲用Dao接口,就可以直接查询数据库了呢 ?其实也是Spring底层对给接口做了动态代理。
(3) 对线上的问题可以进行快速的定位: 当生产上遇到问题时,能够快速的进行定位,这个能力可以快速秒杀别人。
(4) 对面试有很大的好处,特别是BAT大厂,一般都是问道源码级别的,你如果不会,可能第一轮就会被刷掉。
学习Spring源码需要基础吗?
答案是肯定的,需要,那么需要哪些基础呢?
(1)Java 的技术功能
(2) 反射
(3) 设计模式: 简单工厂、工厂方法、单例模式、原型模式、代理模式、策略模式、模板方法模式、委派模式、适配器模式、装饰器模式、观察者模式
(4) Lambda表达式的知识
怎样把Spring源码在本地运行?
(1) git clone https://github.com/spring-projects/spring-framework.git
(2) gradle下载,gradle需要JDK8版本
(3) 到下载的spring源码路径执行gradle命令,gradlew :spring-oxm:compileTestJava
(4) 用idea打开spring源码工程,在idea中安装插件kotlin,重启idea
(5) 把编译好的源码导入到工程中
Bean定义的加载过程
1、首先找到程序的入口
① 找到其构造方法:
② 调用 AnnotationConfigApplicationContext 构造方法,最终会调用父类 GenericApplicationContext的无参方法
③ 调用父类 AnnotationConfigApplicationContext 无参构造方法,生成bean定义读取器和Bean定义扫描器
上面方法的功能是: 实例化注解的Bean定义扫描器,定义类类路径下的bean定义扫描器
3.1 为Bean定义读取器赋值
3.1.1 为容器 中注册系统的Bean定义信息
上面代码主要是注册系统的Bean定义信息,包含以下几种:
① ConfigurationClassPostProcessor
是一个BeanFactory的后置处理器,主要功能是参与BeanFactory的构建,在这个类中,会解析@Configuration的配置类,解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。
② AutowiredAnnotationBeanPostProcessor
AutowiredAnnotationBeanPostProcessor 实现了BeanPostProcessor,当Spring容器启动的时候,AutowiredAnnotationBeanPostProcessor 将扫描Spring容器中的所有Bean,当发现Bean中拥有
@Autowired 注解的时候就会找到与其匹配的Bean,并注入到对应的中去。
那么在什么时候调用的呢?我们可以看下debug堆栈;
③ RequiredAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor 是BeanPostProcessor实现.的,注释应用于bean属性的setter方法,它表明 受影响的bean 属性在配置时是否必须,如果配置了,没有此bean,则容器就会抛出一个BeanInitializationException 异常。
④ .CommonAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor 这个BeanPostProcessor 通过继承 InitDestroyAnnotationBeanPostProcessor 对 @PostConstruct 和 @PreDestroy注解的支持,以及对bean的依赖注入@Resource的支持。
⑤ EventListenerMethodProcessor
使用EventListenerMethodProcessor处理器来解析方法上的 @EventListener;
执行时机: 实在所有Bean都实例化以后执行的
④ 创建类路径下的bean定义扫描器
上述方法 是注册默认的扫描规则
⑤ 读取配置类
上述方法annotatedClasses为我们配置的mainConfig
annotatedClasses 就是MainConfig
此时上面主要解析 MainConfig,解析成BeanDefinition对象
上述的字段都是什么意思呢?
- id: Bean的唯一标识名
- name: 用来为id 创建一个或者多个别名。
- class : 用来定义类的全限定名(包名 + 类名)
- parent: 子类bean定义它所引用它的父类的bean。
- abstract : 默认为false,用来定义bean是否为抽象bean,它表示这个Bean将不会被实例化,一般用于父类Bean,因为父类bean主要供子类bean继承使用。
- lazy-init: 用来定义这个bean是否实现懒初始化。如果为true,它将在BeanFactory启动时初始化所有的单例bean,反之,如果为false,它只在Bean请求使用时才开始创建SingletonBean。
- autowired : 自动装配,它定义了Bean 的自动装配方式。
- depends-on:依赖对象:这个Bean在初始化时依赖的对象,这个对象会在这个Bean初始化之前创建。
- init-method: 用来定义Bean的初始化方法,它会在Bean组装之后调用,它必须是一个无参的构造 方法。
- destroy-method: 用来定义Bean的销毁方法,它在BeanFactory关闭时调用。同样,它也必须是一个无参 的构造方法。只能适用于单例Bean.
- factory-method: 定义创建该Bean对象的 工厂方法。
- factory-bean:定义创建该 Bean对象的工厂类。
那么 BeanDefinitionHolder 又是什么意思呢?
BeanDefinitionHolder 只是封装了BeanDefinition对象,并且添加了beanName 和 alias 属性。
为什么这样设计呢?因为 我们定义bean时,可以定义多个别名的。
BeanDefinitionRegistry 又是什么呢?
BeanDefintion属性来看,我们并没有看到id 和 name属性没有体现在定义中,原因是ID其左右当前Bean的存储key注册到BeanDefinitionRegistry注册器中。name作为别名key注册到AliasRegistry注册中心。最后都是指向其对应的BeanDefinition。
2、AnnotatedBeanDefinitionReader(Bean定义读取)
BeanDefinition 中存储了Bean的信息,而BeanDefintiionRegistry是基于ID和name保存了Bean的定义。从Bean到BeanDefinition然后再注册到BeanDefintionRegistry整个过程。
从上图看出Bean的定义是由AnnotatedBeanDefinitionReader从@Bean的注解中构建出的,然后基于别名注册到BeanDefinitionRegistry。
BeanDefintionReader 的结构图如下:
2.1 bean定义的加载过程
(1) org.springframework.context.support.AbstractApplicationContext#refresh
注册Bean的 代码
invokeBeanFactoryPostProcessors(beanFactory);
(2) org.springframework.context.support.AbstractApplicationContext
#invokeBeanFactoryPostProcessors
然后实例化 容器初始化 的 ConfigurationClassPostProcessor Bean,然后调用其 的postProcessBeanDefinitionRegistry方法
BeanDefinitionRegistryPostProcessor 这个接口的调用分为三部分:
(1) 调用实现PriorityOrdered 排序接口
(2) 调用实现了Ordered排序接口
(3) 没有实现接口的调用
这个接口的理解如下: 获取BeanDefinition 对象,获取到这个对象就可以获取这个对象中注册的 所有BeanDefiniton对象,我们拥有这个对象后,我们就可以对里面所有的BeanDefinition 对象进行修改。
- org.springframework.context.annotation.ConfigurationClassPostProcessor
#postProcessBeanDefinitionRegistry 方法
最终调用
org.springframework.context.annotation.ConfigurationClassParser
最终调用 org.springframework.context.annotation.ConfigurationClassParser
#doProcessConfigurationClass 方法
下面方法主要功能如下:
- 解析 @PropretySource注解
- 解析@ComponentScan注解
- 解析@Import
- 解析@ImportResource
- 解析@Bean methods
- 处理其他
bean定义加载的流程图
总结
Spring 对注解的处理有两种方式:
1、直接将注解Bean注册到容器中
可以在初始化容器的时候注册,也可以在容器创建之后手动调用注册方法向容器中注册,然后通过手动刷新容器,使得容器对注册的注解Bean进行处理
2、通过扫描指定的 包及其子包下的所有类
在初始化注解容器的时指定要自动扫描的路径。如果容器创建以后,如果再向容器中添加注解Bean,则 需要手动调用容器扫描的方法,然后手动刷新容器,使得容器对所注册的Bean进行处理。