• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

mvc-adapter-springcloud: 传统springMVC项目运营一段时间后,由于原有业务与业务的不 ...

原作者: [db:作者] 来自: 网络 收藏 邀请

项目介绍

  前几年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容器的能力
<dependency>    <groupId>com.opages.mvc.adapter</groupId>    <artifactId>adapter-common</artifactId>    <version>1.0.0</version></dependency>
  1. MVC项目applicationContext.xml中配置依赖入口类MVCAutoConfiguration.java,依赖了adapter-common组件后,还需要将入口类交由Spring托管才能实现本组件的所有能力(即:@SpringBootApplication能力)。
<bean class="com.opages.mvc.adapter.common.autoconfigure.MVCAutoConfiguration" />
  1. MVC项目pom.xml依赖adapter-nacos组件(注册中心以nacos为例),实现服务注册与发现
<dependency>    <groupId>com.opages.mvc.adapter</groupId>    <artifactId>adapter-nacos</artifactId>    <version>1.0.0</version></dependency>
  1. 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
  1. MVC项目实现服务间调用,依赖adapter-openfeign
<dependency>	<groupId>com.opages.mvc.adapter</groupId>	<artifactId>adapter-openfeign</artifactId>	<version>1.0.0</version></dependency>
  1. MVC项目配置文件中增加openfeign激活
#激活feign功能,默认false,不开启则不能使用openfeignspring.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);}
  1. 建立服务提供者项目,实现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());	}}

技术说明

  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流程简洁化,以整体来俯瞰全貌,但里面的设计环环相扣,单从某块为出发点来讲都会有所缺失,所以先整体以业务图的方式说明,再标注重点流程(其它并不是不重点,只是关注点不同),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动态代理来返回给容器。

  1. SpringBoot核心技术,利用SPI实现动态加载组件 (待叙...)
  2. SpringBoot条件注入底层实现 (待叙...)
  3. SpringBoot配置文件前缀注入底层实现 (待叙...)
  4. Spring包扫描注入容器的实现 (待叙...)
  5. SpringMVC实现MVC输入输出加解码处理实现 (待叙...)
  6. SpringCloud服务注册实现 (待叙...)
  7. SpringCloud服务发现实现 (待叙...)
  8. Spring集成Feign实现RESTful服务调用 (待叙...)

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap