# mvc-adapter-springcloud **Repository Path**: yoin/mvc-adapter-cloud ## Basic Information - **Project Name**: mvc-adapter-springcloud - **Description**: 传统springMVC项目运营一段时间后,由于原有业务与业务的不断迭代开发,迁移SpringCloud时无法一次性迁移,本组件主要是适配单体系统扩展为单体微服务接入到微服务平台,在不影响业务的情况下一步步迁移直到全部迁移到微服务平台,期间不影响当前业务的解决方案。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 7 - **Forks**: 1 - **Created**: 2021-07-02 - **Last Updated**: 2025-01-12 ## Categories & Tags **Categories**: webframework **Tags**: None ## README #### 项目介绍   前几年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核心机制。 1. **传统MVC项目将拥有什么能力:** + 组件自动依赖的能力 + 自动加载配置文件到Bean中的能力 + 条件注入的能力 + MVC自动服务注册的能力 + MVC自动服务发现的能力 + MVC实现Service层服务调用的能力(MVC与MVC间服务调用,MVC与Cloud间服务调用) 2. **您将了解到什么技术:** + Spring整体流程说明 + SpringBoot核心技术,利用SPI实现动态加载组件 + SpringBoot条件注入底层实现 + SpringBoot配置文件前缀注入底层实现 + Spring包扫描注入容器的实现 + SpringMVC实现MVC输入输出加解码处理实现 + SpringCloud服务注册实现 + SpringCloud服务发现实现 + Spring集成Feign实现RESTful服务调用 #### 软件架构 软件架构说明:大道至简,以下为至简集成传统MVC具备SpringCloud能力的架构图,越是简单内部所需要实现的技术细节越是复杂,后续将从技术结构图角度说明。 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0802/104817_d68a8452_424419.png "mvc.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服务 #### 安装教程 1. MVC项目pom.xml中依赖adapter-common组件,依赖该组件后,项目就拥有了动态装载第三方组件,动态根据配置文件前缀注入配置信息以及条件注入Spring容器的能力 ``` com.opages.mvc.adapter adapter-common 1.0.0 ``` 2. MVC项目applicationContext.xml中配置依赖入口类MVCAutoConfiguration.java,依赖了adapter-common组件后,还需要将入口类交由Spring托管才能实现本组件的所有能力(即:@SpringBootApplication能力)。 ``` ``` 3. MVC项目pom.xml依赖adapter-nacos组件(注册中心以nacos为例),实现服务注册与发现 ``` com.opages.mvc.adapter adapter-nacos 1.0.0 ``` 4. 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 ``` 5. MVC项目实现服务间调用,依赖adapter-openfeign ``` com.opages.mvc.adapter adapter-openfeign 1.0.0 ``` 6. MVC项目配置文件中增加openfeign激活 ``` #激活feign功能,默认false,不开启则不能使用openfeign spring.mvc.openfeign.enable=true #SpringMVC中启动FeignClient包的扫描路径地址 spring.mvc.openfeign.scan=com.opages.mvc.**.api ``` #### 开发说明 1. 建立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); } ``` 2. 建立服务提供者项目,实现API接口实现业务逻辑 ``` @RestController public 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()); } } ``` #### 技术说明 1. **Spring整体流程说明** > 学Spring时常提到的是IOC、AOP,去理解什么是IOC、AOP,然后会讲到Bean交由Spring托管;在讨论到Spring是怎么做到的,很多人还是归结到使用了反射机制,使用了JAVA动态代理或cglib,那如果换种方式理解:IOC和AOP是怎么设计出来的,你会怎么说呢? - 回答上面问题前先看下ApplicationContext的流程,由于本项目应用传统SpringMVC,因此先看下加载xml的上下文ClassPathXmlApplicationContext(Spring版本:4.3.29) ![Spring整体流程图](https://images.gitee.com/uploads/images/2021/0812/100712_df6ea733_424419.gif "spring简化图.gif") - Spring流程分析每次都想把spring流程简洁化,以整体来俯瞰全貌,但里面的设计环环相扣,单从某块为出发点来讲都会有所缺失,所以先整体以业务图的方式说明,再标注重点流程(其它并不是不重点,只是关注点不同),spring源码越分析越觉得自己是弱鸡,如有不对的希望大家留言反馈。下面是关注的重点流程: ![Spring简化流程图](https://images.gitee.com/uploads/images/2021/0812/100813_605283bc_424419.gif "简化流程.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动态代理来返回给容器。 2. **SpringBoot核心技术,利用SPI实现动态加载组件** (待叙...) 3. **SpringBoot条件注入底层实现** (待叙...) 4. **SpringBoot配置文件前缀注入底层实现** (待叙...) 5. **Spring包扫描注入容器的实现** (待叙...) 6. **SpringMVC实现MVC输入输出加解码处理实现** (待叙...) 7. **SpringCloud服务注册实现** (待叙...) 8. **SpringCloud服务发现实现** (待叙...) 9. **Spring集成Feign实现RESTful服务调用** (待叙...)