spring过滤规则的名字含义_Spring/Gradle分项目的迷思和小trick

本文介绍了在不采用微服务的情况下,如何拆分Spring项目以提高效率,提出了依赖管理的四个规则,并分享了使用Gradle进行项目分层与模块拆分的小技巧,包括依赖检查、启动配置简化及调试接口工具的使用。此外,还提到了加速本地编译的Gradle配置方法。
摘要由CSDN通过智能技术生成

前言(太长可以不看):

最近发现业务量上去很大而数据量上去不大.既每次编译很耗时但是直接上微服务又没有必要.毕竟我又不是拿着锤子看啥都是钉子.搞微服务是很傻的事情.

所以就开始考虑怎么拆项目.毕竟以后即使做微服务也一开始也是拆项目对不.而且项目之间管理不好其他一切也是扯淡.毕竟没有技术银蛋.这种东西不做好.到时候就是微服务的引用地狱

所以看了很多架构和方案.设计具体技术方案的没有特别深入.但是为了保证以后无缝升级.还是想了一些东西.这种技术实现最后再讲.当然对前面的分层没有太大想法直接可以看技术的小trick.毕竟这些trick还是挺有意思的

最后本人是kotlin习惯了.不太懂怎么写java.抱歉.不过相信大家都能看懂其中的逻辑

拆项目最关键的是项目之间的关系

项目之间最麻烦的就是依赖.依赖假如是乱的.基本完蛋.所以最关键的就是要把业务边界拆出来.做到高内聚低耦合.实际上业务这么乱.拆都很麻烦.

所以我定了几个规则

1.每一个模块分配Level.

2.只有Level高的(数字低的)可以调用Level低的(数值高的)

3.同级之间不允许互相掉

4.同级之间互相的信息传递走消息队列或者总线模式.

按这个思路我大概拆项目.发现了几个特点.

1. 服务拆分比较容易.但是数据结构的引用比较混乱.所以不得不逼迫我同一层要分配两个.一个是基础的数据项目.另一个才是接口调用项目

2. 同样假如拆分项目.数据结构部分很难通用.所以一些通用工具类方法无法直接用.所以不得不采用 HashMap/Json这种数据结构.同时重新构建通用的数据传递方法

我们业务怎么拆分的可以去看下附件.这里就不具体说了.针对这几个原则.我实现了一下我们的一些小的工具类和接口(gradle和spring).希望能帮到各位.


项目之间分层的小技巧

假如认同我说的这些规则.那么强迫开发者遵守这一套规则就很重要.否则基本上大家能偷懒就偷懒.

首先定义一个module.yml放到项目中去(不直接放到gradle是因为这个文件在Spring运行的时候也会用到).格式如下(具体你自己改).

地址我定义放在每一个项目的:src/main/resources/config/module.yml

module.yml:
module:
  name: qiuqiu-content
  level: 2
  version: 1.0
 subProjects:
       - qiuqiu-content-base

gradle读取起配置并进行检查和把数据放到Project.ext里面

configure(subprojects){
    println("submodule config file check:"+it.name)
    if(!new File(it.getProject().getProjectDir().toString()).exists())
    {
        //创建yml文件...
        return
    }
    //是否存在yml文件
    def ymlFile=new File("${it.getProject().getProjectDir()}/src/main/resources/config/module.yml")
    if(!ymlFile.exists())
    {
        throw new GradleException("请创建子项目的配置文件module.yml.格式请自己参考已有项目,当前错误项目名字:"+it.name)
    }
    //读取yml文件
    def moduleYaml = new org.yaml.snakeyaml.Yaml().load(ymlFile.newInputStream())
    //检查module.yml的名字和gradle的名字是否相同
    if(moduleYaml.module.name!=it.name)
    {
        throw new GradleException("module名字跟yaml配置要相同.gradle项目名字:"+it.name)
    }
    //检查module.yml的版本和gradle的版本是否一致
    if(it.getProject().version.toString()!=moduleYaml.module.version.toString())
    {
        throw new GradleException("项目版本号没有配置.项目是${it.name}," +
                "gradleVersion:${it.getProject().version}," +
                "moduleVersion:${moduleYaml.module.version}," +
                "")
    }
  //Project.ext.moduleLevel把level层级是否相同
    it.getProject().ext.moduleLevel=moduleYaml.module.level
  //检查module.yml的子项目注释和真子项目是否相同
    def gradleSubProjects=it.getProject().subprojects.collect{ sub->sub.name.trim() }
    def ymlSubProjects=moduleYaml.module.subProjects.collect{ name->name.toString().trim() }
    if(ymlSubProjects==null&&gradleSubProjects.size==0)
    {

    }
    else {
        def commons = ymlSubProjects.intersect(gradleSubProjects)
        if(commons.size!=ymlSubProjects.size||commons.size!=gradleSubProjects.size)
        {
            def difference=gradleSubProjects.plus(ymlSubProjects)
            difference.removeAll(commons)
            throw new GradleException("子项目${it.name}依赖没有配置+冲突子项目配置为${difference},请求相应的config文件修改配置")
        }
    }
}

等configure跑完后.整体做依赖的检查.既确保高级不能调用低级.具体实现如下

task dependenciesCheck(){
     //一定要加doLast.保证是最后执行
   doLast {
        otherSubjects.forEach {
            if (!new File(it.getProject().getProjectDir().toString()).exists()) {
                println("${it.getProject().name}项目还在创建中,过滤检查")
                return;
            }
      //上面设置的level
            def projectLevel = it.getProject().ext.moduleLevel;
            //只有compile才能获得相应的依赖.
            def projectDepends = it.getProject().getConfigurations().matching {
                conf->conf.name.endsWith("compile")
            }.collect {
                conf ->
                     conf.getDependencies().matching { dep ->
                         dep.getGroup()!=null&&
                                 dep.getName() != it.getProject().name
                     }
            }.flatten()
      //这里的实现逻辑有问题.下阶段会修改.就是重新通过特殊方法获得此项目的Project
            def depProjects=projectDepends.collect {
                dep->project(dep.group.replace("qiuqiuhuiben.",":").replace(".",":")+":"+dep.name)
            }
            //这里检查level是否是低级还是高级
            depProjects.forEach{
                depproject->
                        def depLevel=depproject.ext.moduleLevel?:0
                        if(depLevel==0||depLevel==null){}
                        else if(depLevel<=projectLevel)
                        {
                            throw new GradleException(
                                    "项目依赖请检查.请Level层次低的项目不要同级和调用层级高的项目," +
                                    "项目名字:${it.getProject().name},"+
                                    "当前Level:${projectLevel}," +
                                    "子项目为:${depproject.name},"+
                                    "子项目成Level为:${depLevel}")
                        }
                        else{}
            }
        }
    }
}

这样就可以确定项目不会出现重复依赖.


拆分模块之间的小技巧

假如想单独启动每一个gradle模块.而并不想每一个项目都配置一个启动类还有相应的配置文件.

1.首先在基础类中生成一个启动类(比如我的就是ModuleDevStarter)然后全体依赖

2. 在项目上层创建文件夹.丢进去配置文件.比如我创建了Project/子项目合集/config然后丢入了通用的config的配置文件

1.配置每一个文件生成jar的gradle脚本(实际少一个部分.打包的jar启动不起来.还在修改)

configure(subprojects)
{
    apply plugin: 'org.springframework.boot'
    apply plugin: 'kotlin'
    apply plugin: 'application'

    dependencies {
                compile project(":qiuqiu-core-packages:qiuqiu-module-base")
        //测试文件依赖
                testCompile project(":qiuqiu-core-packages:qiuqiu-module-base").sourceSets.test.output
            }
    }
     //放在Module里面
    mainClassName = 'com.qiuqiu.huiben.module.ModuleStarter'

    jar {
        baseName = '${it.name}'
        version = '${ext.version}'
    //不想每次编译都打包.就改成false就行
        enabled =  true
        manifest {
            attributes("Implementation-Title": "Gradle")
            attributes("Start-Class": "com.qiuqiu.huiben.module.ModuleStarter")
        }
    }
}

2. 配置启动项目

首先配置下config地址

--spring.config.location=/home/friddle/Project/qiuqiuhuiben/qiuqiu-core-packages/config/

cdd333bd6a04feb1091902537a1dd97c.png

然后选择相应的classPathOfModule就可以单独启动某个jar进行调试了

DevServlet

单独jar启动的时候其实大家都是为了调试.这个工具就提供了非常好用的调试接口工具

先贴代码:

open class RequestData {
    //var serviceName:String?=""
    //var methodName:String?=""
    var arguments: HashMap<String,Any>? =null
}

open class ServiceDevServlet : HttpServlet() {
    val gson = Gson()
  //log4j
    val logger= LoggerFactory.getLogger(ServiceDevServlet::class.java)

    //很简单直接调用嘛
    override fun doPost(req: HttpServletRequest?, resp: HttpServletResponse?) {
        val urls = req!!.pathInfo.replaceFirst("/", "").split("/")
        if (urls.size <= 1) {
            throw IllegalArgumentException("接口调用方式不对")
        }
        var methodName = urls[1]
        var serviceName = urls[0]
        //调用逻辑后直接返回数据.多方便
        try {
            val service = SpringContextHolder.getBean<Any>(beanName = serviceName)!!
            //多个方法不管
            val method = service::class.java.methods.filter { it.name == methodName }.first()
            val requestJsonBody = IOUtils.toString(req.inputStream, "UTF-8");
            val requestJson = requestJsonBody.toGsonObject(RequestData::class.java)!!;
            if(requestJson.arguments==null){requestJson.arguments= hashMapOf()};
            val params = method.parameterTypes
            if (requestJson.arguments!!.size != params.size) {
                throw java.lang.IllegalArgumentException("参数数量不匹配")
            }
            JsonParser()
            val arguments = params.mapIndexed { index, clazz ->
                val jsonString = requestJson.arguments!!.values.toList().get(index).toGsonString()
        //fastjson
                JSON.parseObject(jsonString,clazz)
            }.toTypedArray()
            val methodRsp = method.invoke(service, *arguments)
            val writer = resp!!.writer
            resp.contentType = "application/json;charset=UTF-8"
            resp.characterEncoding = "UTF-8"
            writer.println(methodRsp.toGsonString())
            return
        } catch (e: Exception) {
            val writer = resp!!.writer
            logger.error("erropr",e);
            writer.println(e.message)
        }
    }
}

inline fun <reified T> String.toGsonObject(t: Class<T>): T? {
    try {
        return Gson().fromJson(this, T::class.java)
    }catch (e:Exception)
    {
        logger.error("gson parse error",e)
        return null
    }
}

注册

@Bean
fun serviceDev(@Autowired context: ApplicationContext): ServletRegistrationBean<*>? {
    val registrationBean: ServletRegistrationBean<Servlet> = ServletRegistrationBean<Servlet>(
            ServiceDevServlet()
    )
    registrationBean.addUrlMappings("/dev/*")
    return registrationBean
}

再贴调用

a0a0b5a39c0750745bdad1ba6b15f621.png

简单说下就是:

Post http://localhost:8088/dev/bookServiceImpl/getAllBookByRanks

bookServiceImpl->对应的是beanName

getAllBookByRanks->对应的是方法名字(重名参数不同不管)

{
 "arguments": {
 "page":{"total":20}
 }
}

是参数对象.其实"page"这个key无所谓.关键是顺序不能错.

这样就可以直接用http调试接口了.


Gradle之间的小技巧

gradle build是不是编译很慢很烦人.其实你可以这样:

在本地进行编译且不需要运行jar的时候.可以引入下面的脚本

build.gradle

def profile=System.getenv('profile')
if(profile=='local')
{
    subprojects{
        tasks.withType(Jar){
            task->task.enabled=false
        }
        tasks.whenTaskAdded {
          task->if(
            task.name.contains("bootJar")
            ||task.name.contains("bootDistTar")
            ||task.name.contains("bootDistZip")
            ||task.name.contains("jar")
            ||task.name.contains("distZip")
            ||task.name.contains("distTar")
      //这个是过滤grpcproto的任务的
            ||task.name.contains("extractTestProto")
            ||task.name.contains("extractIncludeTestProto")
            //||task.name.contains("compileTestKotlin")
            //||task.name.contains("compileTestJava")
          ){
                task.enabled=false
          }
        }
    }
}

25f0830d959aa48ea5ff1f82c1038fe4.png

这样就可以过滤掉吃cpu的bootJar/bootDistTar等操作了.本地本来就不需要打包Jar

同时还可以修改gradle.properties

org.gradle.caching=true
//开启缓存

项目分层后测试文件无法通过compile引入.实际上要引入必须通过sourceSets引入

testCompile project(":qiuqiu-core-packages:qiuqiu-module-base").sourceSets.test.output

全局配置就可以这样配置



configure(subprojects)
{
            dependencies {
                compile project(":qiuqiu-core-packages:qiuqiu-module-base")
                testCompile project(":qiuqiu-core-packages:qiuqiu-module-base").sourceSets.test.output
            }
}

附件

这是我项目拆分后的示意图(可供参考)

e58c582f2c282ee739e5898a8a8629ec.png

这是拆分的模块图

a94f3db883d4a27af8c9779aa00c0403.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值