项目介绍 前几年web系统技术选型大多SpringMVC+Spring或Struts2+Spring的单体架构,在实际项目迭代开发过程中,任务紧开发周期短,开发人员能力参差不齐,系统运营几年后会遇到这样一些问题: 1. 业务交织在一起,多个人维护后,可读性较差。 2. 迭代过程中,原有代码与新业务有牵连导致很多僵尸代码无法清除。 3. 历史遗留问题、架构问题,在底层设计不变情况下,一直堆积代码,导致系统越来越臃肿。 4. 系统功能较多,启动时初始化加载资源缓慢,不利于敏捷开发。 5. 调度任务、计算业务、采集业务、数据业务,某个业务出现异常会导致整个系统崩溃。 近年来云原生,微服务Dubbo、SpringCloud技术架构盛行,传统业务想要从单体系统迁移到微服务架构存在如下问题: 1. 迁移的开发成本问题:单体系统迁移微服务架构需要梳理业务、分解业务、划分服务,再进行技术改造迁移,无法一步到位完成迁移工作。 2. 迁移过程保持平台的稳定运营问题:迁移工作无法短时间完成,存在单体架构与微服务架构同时运行的过度状态,如何保证单体架构上迁移微服务过程中的稳定性,即用户无感知。 3. 单体架构在迁移微服务架构的过程中还需要进行新需求的功能开发,如何保持迭代任务与迁移计划同步进行却又不增加迁移的工作量。 4. 单体架构(SpringMVC)与微服务架构(SpringCloud)并行运行,如何让两个架构间交互无障碍,让开发人员无感知的在两种架构间进行业务交互。 基于上述问题,开发了本项目,让单体系统(非SpringBoot、非Dubbo、非SpringCloud)能够快速稳定的接入到云原生微服务架构体系中,并且将会在后续的架构中一步步说明单体系统的服务注册、服务发现、服务调用的技术原理以及模拟SpringCloud过程中的设计与理念。 适用项目 本项目并非系统平台,属于中间件范畴,现在市面上已经有很多SpringCloud架构的开源项目,本项目适用于运营多年的单体系统(非SpringBoot、非Dubbo、非SpringCloud),帮助老项目接入云原生,并且平稳过度到微服务架构,期间会讲解开发项目时借鉴SpringCloud源码的想法理念以及个人的一些集成经验,学习微服务,研究SpringCloud或Dubbo源代码比较抽象,可以借助本项目从简化SpringCloud实现的角度去了解并应用到实际项目中能够更好的去掌握SpringCloud核心机制。 - 传统MVC项目将拥有什么能力:
- 组件自动依赖的能力
- 自动加载配置文件到Bean中的能力
- 条件注入的能力
- MVC自动服务注册的能力
- MVC自动服务发现的能力
- MVC实现Service层服务调用的能力(MVC与MVC间服务调用,MVC与Cloud间服务调用)
- 您将了解到什么技术:
- Spring整体流程说明
- SpringBoot核心技术,利用SPI实现动态加载组件
- SpringBoot条件注入底层实现
- SpringBoot配置文件前缀注入底层实现
- Spring包扫描注入容器的实现
- SpringMVC实现MVC输入输出加解码处理实现
- SpringCloud服务注册实现
- SpringCloud服务发现实现
- Spring集成Feign实现RESTful服务调用
软件架构软件架构说明:大道至简,以下为至简集成传统MVC具备SpringCloud能力的架构图,越是简单内部所需要实现的技术细节越是复杂,后续将从技术结构图角度说明。![mvc.png 输入图片说明](https://images.gitee.com/uploads/images/2021/0802/104817_d68a8452_424419.png) 模块说明mvc-adapter-cloud ├── adapter-common -- MVC适配Cloud公共模块(服务注册与发现定义、自动化依赖、自动化配置、条件注入) ├── adapter-consul -- MVC服务注册与发现(Consul注册中心) ├── adapter-nacos -- MVC服务注册与发现(Nacos注册中心) ├── adapter-openfeign -- MVC服务间调用(MVC间或MVC与Cloud间的大单体服务调用) └── Example -- 测试事例 └── SpringCloud -- SpringCloud案例 ├── CloudCommon -- SpringCloud公共模块 └── CloudDemo -- SpringCloud案例1(CloudDemo) ├── DemoApi -- CloudDemo调用接口 └── DemoService -- CloudDemo服务 └── CloudExample -- SpringCloud案例2(CloudExample) ├── ExampleApi -- CloudExample调用接口 └── ExampleService -- CloudExample服务 └── SpringMVC -- MVC案例 ├── MvcCommon -- MVC公共模块 └── MvcDemo -- MVC案例1(MvcDemo) ├── MvcDemoApi -- MvcDemo调用接口 └── MvcDemoService -- MvcDemo服务 └── MvcExample -- MVC案例2(MvcExample) ├── MvcExampleApi -- MvcExample调用接口 └── MvcExampleService -- MvcExample服务 安装教程- MVC项目pom.xml中依赖adapter-common组件,依赖该组件后,项目就拥有了动态装载第三方组件,动态根据配置文件前缀注入配置信息以及条件注入Spring容器的能力
<dependency> <groupId>com.opages.mvc.adapter</groupId> <artifactId>adapter-common</artifactId> <version>1.0.0</version></dependency> - MVC项目applicationContext.xml中配置依赖入口类MVCAutoConfiguration.java,依赖了adapter-common组件后,还需要将入口类交由Spring托管才能实现本组件的所有能力(即:@SpringBootApplication能力)。
<bean class="com.opages.mvc.adapter.common.autoconfigure.MVCAutoConfiguration" /> - MVC项目pom.xml依赖adapter-nacos组件(注册中心以nacos为例),实现服务注册与发现
<dependency> <groupId>com.opages.mvc.adapter</groupId> <artifactId>adapter-nacos</artifactId> <version>1.0.0</version></dependency> - MVC项目配置文件中增加服务注册与发现的注册中心配置
#激活nacos注册中心spring.mvc.nacos.enable=true#MVC服务定义为一个服务,设置服务名spring.mvc.nacos.discovery.serviceName=mvc-example#nacos注册中心地址spring.mvc.nacos.discovery.serverAddr=http://127.0.0.1:8848#nacos服务版本号spring.mvc.nacos.discovery.metadata[version]=1.0#nacos服务权重spring.mvc.nacos.discovery.metadata[weight]=10 - MVC项目实现服务间调用,依赖adapter-openfeign
<dependency> <groupId>com.opages.mvc.adapter</groupId> <artifactId>adapter-openfeign</artifactId> <version>1.0.0</version></dependency> - MVC项目配置文件中增加openfeign激活
#激活feign功能,默认false,不开启则不能使用openfeignspring.mvc.openfeign.enable=true#SpringMVC中启动FeignClient包的扫描路径地址spring.mvc.openfeign.scan=com.opages.mvc.**.api 开发说明- 建立API服务工程,定义服务API接口(一般来说可以独立API工程,这样如果其它服务调用服务时可以依赖本API工程,就能实现其它服务调用本服务的能力,如果已经存在的服务则只需将原来的url对应API的uri则可实现调用,注意参数一致性,建议使用DTO)
@FeignClient(name="mvc-example")@RequestMapping("/mvc/example")public interface MvcExampleApi { @PostMapping("/get") @ResponseBody public MvcExampleDto getExample(@RequestParam("id") Integer id); @PostMapping(value="/save") public void save(MvcExampleDto exampleDto);} - 建立服务提供者项目,实现API接口实现业务逻辑
@RestControllerpublic class MvcExampleController implements MvcExampleApi { @Override public MvcExampleDto getExample(@RequestParam("id") Integer id) { MvcExampleDto dto = new MvcExampleDto(); dto.setId(id); dto.setUsername("xiaomin"); dto.setPassword("123456"); return dto; } @Override public void save(@RequestBody MvcExampleDto demoDto) { System.err.println("MVC Example保存-->"+demoDto.toString()); }} 技术说明- Spring整体流程说明
学Spring时常提到的是IOC、AOP,去理解什么是IOC、AOP,然后会讲到Bean交由Spring托管;在讨论到Spring是怎么做到的,很多人还是归结到使用了反射机制,使用了JAVA动态代理或cglib,那如果换种方式理解:IOC和AOP是怎么设计出来的,你会怎么说呢?
- 回答上面问题前先看下ApplicationContext的流程,由于本项目应用传统SpringMVC,因此先看下加载xml的上下文ClassPathXmlApplicationContext(Spring版本:4.3.29)![spring简化图.gif Spring整体流程图](https://images.gitee.com/uploads/images/2021/0812/100712_df6ea733_424419.gif) - Spring流程分析每次都想把spring流程简洁化,以整体来俯瞰全貌,但里面的设计环环相扣,单从某块为出发点来讲都会有所缺失,所以先整体以业务图的方式说明,再标注重点流程(其它并不是不重点,只是关注点不同),spring源码越分析越觉得自己是弱鸡,如有不对的希望大家留言反馈。下面是关注的重点流程:
![简化流程.gif Spring简化流程图](https://images.gitee.com/uploads/images/2021/0812/100813_605283bc_424419.gif) - 从上面的流程可以看出,单纯从IOC、AOP讲技术原理并不能说清怎么实现的问题,实现问题需要从设计与整体架构上考虑,也就是常说的整体解决方案; 从Bean的定义(抽象BeanDefinition),Bean的构建(反射构建和FactoryBean构建),Bean的创建过程(实例化与初始化),而核心设计扩展点:后置处理器贯穿始终。IOC与AOP就与后置处理器有关。想让spring做事,需要具备两个条件:①. bean受spring托管,②. 注入的属性(值或对象)受spring管理,而达到这个目的的核心操作是后置处理器。从上面的简化流程图中可以看出spring大量使用后置处理器,后置处理器分两大类: (1). BeanFactoryPostProcessor:干预BeanFactory的创建过程,可以在容器实例化任何其它bean之前读取配置元数据,根据需要进行修改bean的定义属性,需要关注子接口BeanDefinitionRegistryPostProcessor,该接口定义的postProcessBeanDefinitionRegistry方法比父类优先调用,主要用于动态注册符合spring规范的注解(@Component、@Service、@Controller等)生成DebanDefinition到容器中 (2). BeanPostProcessor:干预Bean的创建过程,在Bean实例化进行属性赋值,如:Aware注入、@Autowired注解为属性性赋值、通用属性校验、初始化与销毁方法(@PostConstruct、@PreDestroy)调用,实现底层动态代理。 - 事情总要在共同的意识形态下才可能讨论,经过上面的简略说明,现在可以讲下IOC与AOP的设计了,首先是准备阶段,Spring前期准备了环境变量、解析器、转换器等等,在读取文件、解析文件时应用,将需要的信息比如配置文件或xml文件等解析、同时回调BeanFactoryPostProcessor,先执行子接口BeanDefinitionRegistryPostProcessor将使用注解Bean解析出来,最终解析成BeanDefinition,再执行BeanFactoryPostProcessor接口,将有关Bean属性值做一些修改,默认内部实现接口: - ConfigurationClassPostProcessor:配置类后置处理器,解析加了@Configuration的配置类,解析@ComponentScan、@ComponentScans注解扫描的包,解析@Import、@Bean注解。- PropertyPlaceholderConfigurer:在XML配置文件中加入外部属性文件,${key}替换指定的properties文件中的值。 - PropertyOverrideConfigurer: 允许对Spring容器中配置的任何我们想处理的bean定义的property信息(beanName.propertyName=value)进行覆盖替换。 - CustomEditorConfigurer:类型转换器,可以根据对象类型取得与其相对应的PropertyEditor来做具体的类型转换。
- 处理完Bean属性后,根据BeanPostProcessor(子接口InstantiationAwareBeanPostProcessor)后置处理器,在实例化前后,初始化前后做动态扩展(注入属性、创建代理、执行初始化方法、注入销毁方法),主要后置处理器有: - CommonAnnotationBeanPostProcessor:重写postProcessPropertyValues,注入@Resource - AutowiredAnnotationBeanPostProcessor: 重写postProcessPropertyValues,注入@Autowire - PersistenceAnnotationBeanPostProcessor:用于注入相应的JPA资源 - AbstractAutoProxyCreator:初始化前(postProcessBeforeInstantiation)返回指定代理对象的动态代理(不常用),初始化后(postProcessAfterInitialization)返回常规动态代理(Spring AOP) - CommonAnnotationBeanPostProcessor:收集@PostConstruct,@PreDestroy(初始化方法与销毁方法),注入@Resource注解的类 - ImportAwareBeanPostProcessor:注入ImportAware子类的AnnotationMetadata
经过上述处理后,Bean创建成功再放入缓存中,下次直接从缓存中获取。 - AOP则是在上面BeanPostProcessor的子类实现中动态扩展进来的 (1). 前置条件:配置类中注解@EnableAspectJAutoProxy,该注解属性@Import(AspectJAutoProxyRegistrar.class)导入了AspectJAutoProxyRegistrar类,该类继承ImportBeanDefinitionRegistrar,因此能自定义给容器注入BeanDefinetion -> AnnotationAwareAspectJAutoProxyCreator,而注入的类正是上面后置处理器AbstractAutoProxyCreator的子类,该类又继承了BeanFactoryAware,因为在设置BeanFactory时会创建advisor工厂类(ReflectiveAspectJAdvisorFactory)和构造类(BeanFactoryAspectJAdvisorsBuilderAdapter)用于构建Aspect对象 (2). 在Bean实例化后,初始化前(postProcessBeforeInstantiation)回调时判断是否能代理(是否@Aspect,是否实现Advice、Pointcut、Advisor、AopInfrastructureBean,有则不被代理),判断时就查找@Aspect注解类,解析生成Advisor对象(由ReflectiveAspectJAdvisorFactory.getAdvisors()方法根据是否存在pointCut构造),如果存在pointCut则将切点表达式expressionPointcut、ReflectiveAspectJAdvisorFactory、方法名包装成advisor对象(InstantiationModelAwarePointcutAdvisorImpl),InstantiationModelAwarePointcutAdvisorImpl构造方法会触发构造通知对象(对应的注解生成Before.class,Around.class,After.class,AfterReturning.class, AfterThrowing.class,Pointcut.class),通过通知的构造方法,将通知增强方法,切面表达式传入到通知当中,然后Advisor存入advisedBeans(如果没有自定义目标类则就止结束) 创建advisor的逻辑发生在扩展接口中的postProcessBeforeInstantiation,实例化之前执行,如果有自定义的TargetSource指定类,则则直接生成代理类,并直接执行初始化之后的方法postProcessAfterInitialization。这种情况使用不多,常规代理类还是在postProcessAfterInitialization中创建,也就是IOC最后一个扩展方法。 (3). 在Bean初始化后回调AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization方法,深入会调用getAdvicesAndAdvisorsForBean()获取到合适的advisor,该方法最终会调用findEligibleAdvisors(),然后根据AopUtils类筛选规则(遍历被代理类的所有的方法,跟切面表达式进行匹配,如果有方法匹配到,也就意味着该类会被代理,如果代理目标是接口或者Proxy类型,则走jdk类型,否则使用cglib类型,springboot1.x与2.x有所不同),最终返回代理类。 用一句话来总结:激活注解AnnotationAwareAspectJAutoProxyCreator时创建AnnotationAwareAspectJAutoProxyCreator,该类继承BeanPostProcessor和实现BeanFactoryAware,所以又创建Advisor工厂,在Bean实例化后使用Advisor工厂创建Advisor对象时也构造增强通知类,在Bean初始化后根据AopUtils规则匹配pointCup表达式来创建目标类的JDK或Cglib动态代理来返回给容器。
- SpringBoot核心技术,利用SPI实现动态加载组件 (待叙...)
- SpringBoot条件注入底层实现 (待叙...)
- SpringBoot配置文件前缀注入底层实现 (待叙...)
- Spring包扫描注入容器的实现 (待叙...)
- SpringMVC实现MVC输入输出加解码处理实现 (待叙...)
- SpringCloud服务注册实现 (待叙...)
- SpringCloud服务发现实现 (待叙...)
- Spring集成Feign实现RESTful服务调用 (待叙...)
|
请发表评论