一个项目的SpringCloud微服务改造过程

SSO是公司一个已经存在了若干年的项目,后端采用SpringMVC、MyBatis,数据库使用MySQL,前端展示使用Freemark。今年,我们对该项目进行了一次革命性的改进,将其改造成SpringCloud架构,并且把前后端分离,前端采用Vue框架。

创新互联是一家专业提供曲阜企业网站建设,专注与成都网站制作、成都网站建设、H5响应式网站、小程序制作等业务。10年已为曲阜众多企业、政府机构等服务。创新互联专业网站制作公司优惠进行中。

一、使用SpringCloud架构进行改造

1.1 为什么使用SpringCloud

SpringCloud的核心是SpringBoot,相比较于传统的Spring,SpringCloud具有以下优点:

  • 部署简单,SpringBoot内置了Tomcat容器,可以将程序直接编译成一个jar,通过java-jar来运行。
  • 编码简单,SpringBoot只需要在pom文件中添加一个starter-web依赖,即可帮助开发者快速启动一个web容器,非常方便。
  • 配置简单,SpringBoot可以通过简单的注解方式来代替原先Spring非常复杂的xml方式。如果我想把一个普通的类交给Spring管理,只需要添加@Configuration和@Bean两个注解即可。
  • 监控简单,我们可以引入spring-boot-start-actuator依赖,直接使用REST方式来获取进程的运行期性能参数,从而达到监控的目的。

1.2 一个常规项目都需要改造哪些部分

1.2.1 配置文件  

SSO项目改造前充斥着大量的配置文件,主要包含以下这些部分:

  • 静态资源相关
  • 数据源
  • mybatis配置
  • redis配置
  • 事务
  • 拦截器拦截内容
  • 监听器、过滤器
  • 组件扫描路径配置

本文着重介绍以下几个部分:

1)静态资源处理

SpringMVC中,如果
mvc:interceptors
配置的URL规则如下,则不会拦截静态资源。

 
 
 
 

但是如果配置的是:

 
 
 
 

方案1
: 在web.xml中配置
default
,用
defaultServlet
先处理请求如:

 
 
 
 
  1.         default
  2.         *.jpg
  3.     
  4.     
  5.         default
  6.         *.png
  7.     
  8.     
  9.         default
  10.         *.gif
  11.     
  12.     
  13.         default
  14.         *.ico
  15.     
  16.     
  17.         default
  18.         *.gif
  19.     
  20.     
  21.         default
  22.         *.js
  23.     
  24.     
  25.         default
  26.         *.css
  27.     

 方案2:使用标签声明静态资源路径

 
 
 
 

方案3:使用mvc:default-servlet-handler/标签

SpringBoot解决方案:继承WebMvcConfigurerAdapter实现addResourceHandlers方法。

 
 
 
 
  1. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  2.     registry.addResourceHandler("/**")
  3.     .addResourceLocations("classpath:/resource/")//sso静态资源
  4.     .addResourceLocations("classpath:/META-INF/resources/")//swagger静态资源
  5.     .setCachePeriod(0);//0表示不缓存
  6. }

 sso静态资源文件路径如图:

2)拦截器

SpringMVC配置文件内容:

拦截任何请求并且初始化参数,有些请求是不需要拦截的,有的请求登录后不需要经过权限校验直接放行。

 
 
 
 
  1.     
  2.         
  3.            
  4.            
  5.           
  6.           请求地址
  7.           
  8.           
  9.           
  10.           请求地址
  11.           
  12.          
  13.    
  14.  

 SpringBoot中添加拦截器只需继承WebMvcConfigurerAdapter,并重写addInterceptors方法即可。

 
 
 
 
  1. /*** 拦截器
  2. * @param registry
  3. */
  4. @Override
  5. public void addInterceptors(InterceptorRegistry registry) {
  6.    registry.addInterceptor(permissionInterceptor).
  7.            addPathPatterns("/**");
  8.    super.addInterceptors(registry);
  9. }

自定义的拦截器需要初始化一些参数,因此需要在注册拦截器之前注册,这里我们设置为懒加载。免登录拦截的路径,以及登录后不需要判断权限的路径都写在yml文件了,通过系统环境变量Environment获取值。

 
 
 
 
  1. @Autowired 
  2. @Lazy 
  3. private PermissionInterceptor permissionInterceptor; 
  4. @Autowired 
  5. private Environment environment; 
  6. /**
  7. *
  8. */
  9. @Bean
  10. public PermissionInterceptor permissionInterceptor() {
  11. PermissionInterceptor permissionInterceptor = new PermissionInterceptor();
  12. List excludeUrls = Arrays.asList(environment.getProperty("intercept.exclude.path").split(","));
  13. List commonUrls = Arrays.asList(environment.getProperty("intercept.login.exclude.path").split(","));
  14. permissionInterceptor.setCommonUrls(commonUrls);
  15. permissionInterceptor.setExcludeUrls(excludeUrls);
  16. return permissionInterceptor;
  17. }

 3)数据库和MyBatis配置

A、数据源配置
数据源注入的三种情况:
【情况一】

  • 条件:不引⼊druid-spring-boot-starter只依赖druid.jar,不指定spring.datasource.type。

  • 结果:注入的数据源是tomcat的数据源。

  • 解析:依赖的mybatis-spring-boot-starter工程依赖了tomcat的数据源,spring-boot-autoconfigure-starter的DataSourceAutoConfiguration自动注入类会在不指定数据源的情况下,判断路径中是否存在默认的4种数据源(Hikari,Tomcat,Dbcp,Dbcp2)的其一,如果有就注入。

【情况二】

  • 条件: 不引入druid-spring-boot-starter只依赖druid.jar ,指定spring.datasource.type为DruidDataSource。
  • 结果: 注入了DruidDataSource数据源,但配置文件中的druid配置不会生效。  
  • 解析: 指定了依赖的数据源后,spring自动注入的starter会将指定的数据源注入,yml指定了druid数据源。 @ConfigurationProperties注解的DataSourceProperties没处理druid部分的性能参数属性,只处理了数据源部分的属性。

 【情况三】

  • 条件: 引⼊ druid-spring-boot-starter 不依赖druid.jar,指定spring.datasource.type为DruidDataSource。
  • 结果: 注入了DruidDataSource数据源, 配置文件中的druid配置也会生效。  
  • 解析: druid-spring-boot-starter自动配置类会在DataSourceAutoConfiguration之前先创建数据源,并且@ConfigurationProperties注入的DataSourceProperties包含了配置文件中druid的属性。

 pom.xml依赖:

 
 
 
 
  1.   
  2.       
  3.       
  4.       
  5.   
  6.   
  7.       com.alibaba
  8.       druid-spring-boot-starter
  9.       1.1.10
  10.   
  11.   
  12.       org.mybatis.spring.boot
  13.       mybatis-spring-boot-starter
  14.       RELEASE
  15.   

yml配置:

 
 
 
 
  1. spring:
  2.   datasource:
  3.     type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
  4.     driver-class-name: com.mysql.jdbc.Driver            # mysql驱动包
  5.     url: jdbc:mysql://yourURL        # 数据库名称
  6.     username: yourusername
  7.     password: yourpassword
  8.     druid:
  9.       initial-size: 5  # 初始化大小
  10.       min-idle: 5  # 最小
  11.       max-active: 20  # 最大
  12.       max-wait: 60000  # 连接超时时间
  13.       time-between-eviction-runs-millis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  14.       min-evictable-idle-time-millis: 300000  # 指定一个空闲连接最少空闲多久后可被清除,单位是毫秒
  15.       validationQuery: select 'x'
  16.       test-while-idle: true  # 当连接空闲时,是否执行连接测试
  17.       test-on-borrow: false  # 当从连接池借用连接时,是否测试该连接
  18.       test-on-return: false  # 在连接归还到连接池时是否测试该连接
  19.       filters: config,wall,stat

 B、MyBatis配置

通过引入mybatis-spring-boot-starter依赖,可以简单配置mybatis上手使用。
下面简单分析mybatis-starter的源码以及如何配置mybatis。
 
先看mybatis-spring-boot-starter中mybatis-spring-boot-autoconfigure的spring.factories文件。

 
 
 
 
  1. # Auto Configure
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

 可以看到自动注入类是MybatisAutoConfiguration,我们从这个类入手分析可以知道,必须先创建好了数据源后,才会加载MyBatis的sqlSessionFactory。

@EnableConfigurationProperties({MybatisProperties.class})注解指定了配置文件中 prefix = "mybatis" 那部分属性有效,这部分属性值将注入到已创建的SqlSessionFactoryBean中,最后生成SqlSessionFactory对象。

 
 
 
 
  1. @Configuration
  2. //当SqlSessionFactory,SqlSessionFactoryBean存在的情况下加载当前Bean
  3. @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
  4. //当指定数据源在容器中只有一个或者有多个但是只指定选择数据源
  5. @ConditionalOnSingleCandidate(DataSource.class)
  6. @EnableConfigurationProperties({MybatisProperties.class})
  7. //当数据源注入到Spring容器后才开始加载当前Bean
  8. @AutoConfigureAfter({DataSourceAutoConfiguration.class})
  9. public class MybatisAutoConfiguration implements InitializingBean {
  10.     private final MybatisProperties properties;
  11.     @Bean
  12.     @ConditionalOnMissingBean
  13.     public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  14.         SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  15.         factory.setDataSource(dataSource);
  16.         factory.setVfs(SpringBootVFS.class);
  17.        //设置mybatis配置文件所在路径
  18.         if (StringUtils.hasText(this.properties.getConfigLocation())) {
  19.           factory.setConfigLocation(this.resourceLoader.getResource
  20.           (this.properties.getConfigLocation())); }
  21.         }
  22.       //设置其他MyBatisProperties对象中有的属性略....
  23.        return factory.getObject();
  24.    }
  25. }

 MybatisProperties含有的属性:

 
 
 
 
  1. @ConfigurationProperties(prefix = "mybatis" )
  2. public class MybatisProperties {
  3.  public static final String MYBATIS_PREFIX = "mybatis";
  4.  private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
  5.  private String configLocation;
  6.  private String[] mapperLocations;
  7.  private String typeAliasesPackage;
  8.  private Class typeAliasesSuperType;
  9.  private String typeHandlersPackage;
  10.  private boolean checkConfigLocation = false;
  11.  private ExecutorType executorType;
  12.  private Properties configurationProperties;
  13.  @NestedConfigurationProperty
  14.  private Configuration configuration;
  15. }

 C、使用MyBatis

  • 配置文件

application.yml

 
 
 
 
  1. mybatis:
  2. config-location: classpath:mybatis.xml        # mybatis配置文件所在路径
  3. type-aliases-package: com.creditease.permission.model    # 所有Entity别名类所在包
  4. mapper-locations: classpath:mybatis/**/*.xml

 从上面的MybatisProperties可以看出,mybatis可以指定一些configuration,比如自定义拦截器pageHelper。

mybatis.xml

 
 
 
 
  1.     "http://mybatis.org/dtd/mybatis-3-config.dtd">
  2.     
  3.         
  4.         
  5.     
  • 在启动类上加入@MapperScan注解
 
 
 
 
  1. @MapperScan("com.creditease.permission.dao")//mapper类所在目录
  2. public class SsoApplication {
  3.     public static void main(String[] args) {
  4.         SpringApplication.run(SsoApplication.class, args);
  5.     }
  6. }
4)事务 

Spring事务有两种处理方式:

  • 编程式

用TransactionTemplate或者直接使用底层的PlatformTransactionManager将事务代码写在业务代码中。
优点:
可以在代码块中处理事务,比较灵活。
 
缺点:对代码具有侵入性。

  • 声明式

采用@Transactional注解或者基于配置文件方式,在方法前后进行拦截。
优点:
非侵入性不会污染代码。
缺点:
事务只能在方法和类上控制,粒度较小。
A、使用@Transactional注解
非SpringBoot工程,需要在配置文件中加入配置:

 
 
 
 


SpringBoot工程可以用@EnableTransactionManagement注解代替上面的配置内容。
B、采用配置文件方式 
之前的sso是基于配置的方式,配置代码如下:

 
 
 
 
  1.       
  2.       
  3.   
  4.   
  5.       
  6.           
  7.           
  8.           
  9.           
  10.           
  11.           
  12.       
  13.   

 改造后的SpringBoot基于Java代码:

 
 
 
 
  1.  @Aspect
  2. @Configuration
  3. public class TransactionAdviceConfig {
  4. /**
  5. * 指定切入点
  6. */
  7. private static final String AOP_POINTCUT_EXPRESSION = "execution(public * com.creditease.permission.service.impl.*Impl.*(..))";
  8. @Resource
  9. DruidDataSource dataSource;
  10. /**
  11. * 指定处理事务的PlatformTransactionManager
  12. * @return
  13. */
  14. @Bean
  15. public DataSourceTransactionManager transactionManager() {
  16. return new DataSourceTransactionManager(dataSource);
  17. }
  18. /**
  19. * 指定切入点处理逻辑,执行事务
  20. * @return
  21. */
  22. @Bean
  23. public TransactionInterceptor txAdvice() {
  24. DefaultTransactionAttribute txAttrRequired = new DefaultTransactionAttribute();
  25. txAttrRequired.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  26. DefaultTransactionAttribute txAttrRequiredReadonly = new DefaultTransactionAttribute();
  27. txAttrRequiredReadonly.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  28. txAttrRequiredReadonly.setReadOnly(true);
  29. NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
  30. source.addTransactionalMethod("query*", txAttrRequiredReadonly);
  31. source.addTransactionalMethod("find*", txAttrRequiredReadonly);
  32. source.addTransactionalMethod("save*", txAttrRequired);
  33. source.addTransactionalMethod("delete*", txAttrRequired);
  34. source.addTransactionalMethod("add*", txAttrRequired);
  35. source.addTransactionalMethod("modify*", txAttrRequired);
  36. return new TransactionInterceptor(transactionManager(), source);
  37. }
  38. /**
  39. * Advisor组装配置,将Advice的代码逻辑注入到Pointcut位置
  40. * @return
  41. */
  42. @Bean
  43. public Advisor txAdviceAdvisor() {
  44. AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
  45. pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
  46. return new DefaultPointcutAdvisor(pointcut, txAdvice());
  47. }
5)全局异常处理

一般编码时有异常我们都会try-catch捕获异常,有时为了区分不同的异常还会一次catch多个异常,大量的try-catch语句,这样使得代码也不够优雅;
一个相同的异常处理写多次代码也比较冗余,所以引入全局的异常处理非常必要。
改造前的异常处理配置文件:

 
 
 
 
  1.         
  2.             
  3.                 /permission/noSecurity
  4.             
  5.         
  6.  

使用SimpleMappingExceptionResolver类处理异常,设置自定义异常类型NopermissionException,以及异常发生后的请求路径/permission/noSecurity。

SpringBoot中采用@RestControllerAdvice或者@ControllerAdvice设置全局异常类。这两者区别类似于@Controller和@RestController注解。
SSO中定义了三种全局的异常处理:普通的Exception处理;自定的NopermissionException异常和参数校验异常。

全局异常处理代码如下:

 
 
 
 
  1. @Configuration
  2. @Slf4j
  3. @RestControllerAdvice
  4. public class GlobalExceptionConfig {
  5. //无权限处理
  6. @ExceptionHandler(value = {NopermissionException.class})
  7. public void noPermissionExceptionHandler(HttpServletRequest request, Exception ex, HttpServletResponse response, @Value("${sso.server.prefix}") String domain) throws IOException {
  8. printLog(request,ex);
  9. response.sendRedirect("跳转到无权限页面地址");
  10. }
  11. //参数校验处理
  12. @ExceptionHandler(value = {BindException.class})
  13. public ResultBody BindExceptionHandler(BindException bindException){
  14. List errors = bindException.getBindingResult().getAllErrors();
  15. //这个ResultBody是一个返回结果对象,这里需要返回json,里面包含了状态码和提示信息
  16. return ResultBody.buildFailureResult(errors.get(0).getDefaultMessage());
  17. }
  18. //所有未捕获的异常处理逻辑
  19. @ExceptionHandler(value = {Exception.class})
  20. public ResultBody exceptionHandler(HttpServletRequest request,Exception ex){
  21. printLog(request,ex);
  22. return ResultBody.buildExceptionResult();
  23. }
  24. //将请求参数和异常打印出来,结合@slf4j注解
  25. public void printLog(HttpServletRequest request,Exception ex){
  26. String parameters = JsonHelper.toString(request.getParameterMap());
  27. log.error("url>>>:{},params>>>:{} ,printLog>>>:{}",request.getRequestURL(),parameters,ex);
  28. }
  29. }

@RestControllerAdvice结合@Validation,可以对Bean进行校验,校验不通过会抛出BindException异常。通过注解可以少写if-else代码,判断请求的接口参数是否为空,提高代码的美观性。例如:

 
 
 
 
  1.  //常规做法
  2.     if(StringUtils.isEmpty(ssoSystem.getSysCode())
  3. //SSO做法
  4. //在Controller请求方法上添加@Valid注解
  5. @RequestMapping(value = "/add", method = RequestMethod.POST)
  6. public ResultBody add(@Valid @RequestBody SsoSystem ssoSystem) {
  7. }
  8. //在需要处理的SsoSystem Bean的属性上加@NotNull注解
  9. @NotNull(message = "系统编号不能为空")
  10. private String sysCode;

 当sysCode传入参数为空时,就会抛出BindException被全局的异常处理类,捕获处理返回json格式的参数:

 
 
 
 
  1. {
  2.     "resultCode":2,
  3.     "resultMsg":"系统编号不能为空",
  4.     "resultData":null
  5. }

1.3 注意事项

1.3.1 内置tomcat版本太高引发的问题 

SpringBoot1.5默认使用内嵌tomcat8.5版本,而原来SpringMVC的SSO部署在tomcat7上。tomcat的升级对这次改造影响最明显的就是cookie。tomcat8后采用的cookie校验协议是Rfc6265CookieProcessor。该协议要求domain的命名必须遵循以下规则:

  • 必须是1-9、a-z、A-Z、. 、- 这几个字符组成。
  • 必须是数字或字母开头 (之前是以.creditease.corp 会报错tomcat cookie domain validation异常,最后改成了 creditease.corp)。
  • 必须是数字或字母结尾。

二、前后端分离
2.1 解决跨域问题

由于是两个不同的应用,必然会有两个不同的端口。不同的端口就会有跨域问题,SSO采用的方式是通过nginx区分来自前后端的请求,反向代理请求对应到不同的服务去。

  • sso.creditease.com对应的是后端的应用服务。
  • sso.creditease.com/web对应的是前端的静态资源应用服务。

2.2 方便联调效率,引入swagger 

swagger是后端接口展示的插件,通过修改拦截器代码,mock登录对象免登录,直接访问接口进行前后端的调试。在swagger插件上可以看到具体接口请求路径和参数、参数是否必须、返回值、接口统计信息等。

  • 接口统计信息
  • 请求参数和路径

  • 返回值

2.3 跳转接口修改

之前是通过SpringMvc的modeAndview方式跳转的,现在做了两种处理:

  • 改成restful接口的形式,前端控制跳转然后直接获取数据。  
  • 直接通过response.sendRedirect跳转页面。

 注意:老代码跳转采用的是通过SpringMvc在return的页面路径前加redirect的形式,如:return "redirect:index",这样默认会在return的URL后加jessionID。

2.4 静态资源地址变更可能引发的问题 

特别需要注意代码中的相关校验路径的地方。比如在这次改造过程中路径修改会影响以下几个方面。

  • 菜单权限校验的时候,之前人、角色和路径已经绑定了,修改菜单访问路径会导致没权限。
  • 扫码登录的接口判断了refer来源,修改路径会导致请求失败。
  • 之前的sso-dome工程引用了静态资源,修改路径会报404。

【本文是专栏机构宜信技术学院的原创文章,微信公众号“宜信技术学院( id: CE_TECH)”】

新闻标题:一个项目的SpringCloud微服务改造过程
文章来源:http://www.shufengxianlan.com/qtweb/news8/273658.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联