# 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能力的架构图,越是简单内部所需要实现的技术细节越是复杂,后续将从技术结构图角度说明。

#### 模块说明
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流程分析每次都想把spring流程简洁化,以整体来俯瞰全貌,但里面的设计环环相扣,单从某块为出发点来讲都会有所缺失,所以先整体以业务图的方式说明,再标注重点流程(其它并不是不重点,只是关注点不同),spring源码越分析越觉得自己是弱鸡,如有不对的希望大家留言反馈。下面是关注的重点流程:

- 从上面的流程可以看出,单纯从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服务调用** (待叙...)