前几年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具备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服务
<dependency>
<groupId>com.opages.mvc.adapter</groupId>
<artifactId>adapter-common</artifactId>
<version>1.0.0</version>
</dependency>
<bean class="com.opages.mvc.adapter.common.autoconfigure.MVCAutoConfiguration" />
<dependency>
<groupId>com.opages.mvc.adapter</groupId>
<artifactId>adapter-nacos</artifactId>
<version>1.0.0</version>
</dependency>
#激活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
<dependency>
<groupId>com.opages.mvc.adapter</groupId>
<artifactId>adapter-openfeign</artifactId>
<version>1.0.0</version>
</dependency>
#激活feign功能,默认false,不开启则不能使用openfeign
spring.mvc.openfeign.enable=true
#SpringMVC中启动FeignClient包的扫描路径地址
spring.mvc.openfeign.scan=com.opages.mvc.**.api
@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);
}
@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());
}
}
学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动态代理来返回给容器。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
1. 开源生态
2. 协作、人、软件
3. 评估模型