我在极速一个月学完黑马的《java web》课程之后跟着他写了一个java后端项目,但是后面我才发现那只是为了巩固基础的一个简单课程项目,跟实际开发的项目根本不一样。然后后面我暑假去了超星的移动图书馆开发部实习(我主要做前端的),我还是问团队老大哥要了企业的后端项目学习,顺便一起跟着黑马的《苍穹外卖》项目一起学,那么我将讲解两种项目结构,深入了解企业级项目的结构。
《苍穹外卖》项目结构:
超星移动图书馆项目结构(只显示结构,不泄露代码内容):
一、《苍穹外卖》的分三大模块结构
1、回顾基础
那么我们回顾基础,javaweb里讲过一个基础的java项目必须分成三层架构,首先资源数据定义成叫pojo的类(作为接收前端发来的数据、存入数据库、再封装好返回给前端的数据),然后分别是【Controller控制层】、【Service业务层】、【Dao数据层】
2、分模块
具体为什么呢?他也没细说,我的理解是企业的大型项目时就会需要分模块开发,然后不同的模块可能是不同的人分工写,也或者是不同模块分不同时期阶段编写,同时分模块化的项目也便于维护,分得越细越容易找到哪里出问题。(至于这里苍穹外卖其实并不是一个大型项目,尤其是后台管理系统,只是为了方便大家学习这种模块化开发的理念)
那么这个项目资源可以去黑马那里下载,自行配置,这里不过多叙述,如果想自己照着它配置一个分模块化的项目时,可以参考这个博主的文章,讲得比较详细:《苍穹外卖》知识梳理P1-多模块项目的创建_sky-common-CSDN博客
首先整个项目的父工程项目叫“sky-take-out”,它自身是一个maven管理的父工程项目。sky-take-out中并没有任何内容,只是为了实现统一管理依赖版本,以及聚合其他子模块。
然后就是三大模块:sky-common模块,sky-pojo模块,sky-server模块,这三个模块分别也都是maven管理的工程项目,然后最终都从属于一个父模块:sky-take-out
模块名称 | 模块作用 |
---|---|
common | 子模块,存放公共类,例如:工具类、常量类、异常类等 |
pojo | 子模块,存放实体类、VO、DTO等 |
server | 子模块,后端服务,存放配置文件、Controller、Service、Mapper等 |
那么我们可以从上面表格对三个模块的简单解释,可以看出,其实
【service模块】是专门对应了【controller、service、mapper】这三层的业务处理;
然后我们之前学的javaweb里有自己手动创建了很多其他的目录,最基础的就是【pojo实体类】,放到【pojo模块】;
然后就是什么【utils工具类】、【config配置类】、【filter过滤器】、【Interceprter拦截器】......一堆杂七杂八的目录,那么这些公用的包目录就可分别放到【common模块】,不是公用的就还是放回【service模块】
二、详细解析三大模块内容
这一部分我希望各位直接死记硬背,不要管他为什么这样,逻辑关系啥的,因为他就是一种规范而已,当然我也会简单讲一下为什么吧。
1、pojo模块
这个模块是最简单的,就是一堆实体类而已。
在我们之前学的内容里,为了对应数据库的每一个【表】的数据,我们对应在java也实现类对应的【类】,然后在接收前端接口传过来的数据、响应返回给前端的数据,我们也通常是封装在这些类里,当然也还有针对“分页查询类”、“规范的响应类”这种数据类。
但是在写代码里我们就会发现有的时候前端发来的数据、操作数据库的数据、返回响应的数据并不一致,打个比方:用户注册的业务里,前端可能只需要传“账户名”、“账户密码”、“姓名”;然后操作数据库需要对应生成并传入这个用户的“id”、“账户名”、“账户密码”、“创建时间”、“更新时间”......;最后返回前端的时候只需返回一个规范的Result类,有返回数据要求的就把整个用户信息返回、没有的就不用返回具体数据。
那这样直接用一个 “用户类” 接收、封装这些数据就会很乱,那就要专门再细分这个 “用户类” ,分出三个小类:【dto】、【entity】、【vo】
【dto】
【entity】
另外:把dto的值封装进entitty的快捷方法
【vo】
总结,pojo就是放除了【Result类】跟【PageBean类】的所有类的模块,然后所有类里又分三个小类:【dto】、【entity】、【vo】,【dto】一般是controller跟service方法的接收参数,【entity】是service的操作对象以及mapper的操作对象,【vo】是controller的返回响应数据
2、common模块
这个是个很重要的【公用配置】的模块,很细我将细细分析
整体结构简单来说就是:
名称 | 说明 |
---|---|
constant | 存放相关常量类 |
context | 存放上下文类 |
enumeration | 项目的枚举类存储 |
exception | 存放自定义异常类 |
json | 处理json转换的类 |
properties | 存放SpringBoot相关的配置属性类 |
result | 返回结果类的封装 |
utils | 常用工具类 |
(1)result
前面我在pojo模块已经讲过了,就不再多解释,这里注意展开讲一下【Result】和【PageResult】跟我们之前学的又有什么不同
【PageResult】
【Result类】
那么现在属性还是这三,方法也还是这三,有2个地方不一样:
1、属性data、方法返回值都是【泛型】;而类也不再是【普通类】,而是【泛型类】
前面我们学过,Result类的data用来接收任意一种类型是数据结果,不过我们之前学的是用【Object】来接收任意类型的数据:
泛型规定了一种规范、安全性,一旦规定了这个【泛型】具体指什么,你就得按这个标准来填数据
那么我们这里的Result类就得换成泛型,更加的安全、规范,这样一来就能在外面定义了Result类的时候,一旦规定了这个Result类的【T】是什么类型,那后面这个【Result<T>】实例化的对象就只能用定义时规定好的数据!而且会有代码提示!
参考详细文章:https://zhuanlan.zhihu.com/p/331620060
2、第二个不同的地方,跟PageResult一样,要加一个【implement Serializable】,实现【序列化】接口
那么关于序列化、反序列化的作用解释,我也专门写了一篇文章,还请自行阅读完再回来搭配理解,文章链接:(可以暂时只看到第二大点就够了)java里的序列化反序列化、HttpMessageConverter、Jackson、消息转化器、对象转化器...都是啥?-CSDN博客
(2)constant
这个目录存放的就是常量了,在企业项目开发需要用到很多很多常量,我们不能像写一个java文件时那样直接写默认值,否则要更改默认值的时候那么文件里找就会很痛苦。
比如一个已注册用户有两种状态,1代表正常用户,可以使用软件;0表示禁用的用户,可能发了什么不当言论啊、看黄片啊啥的导致他被封了,不能正常使用软件;
那很多地方我们都会对应用户的状态进行判断,如果全都是0、1,那后期我们想把正常状态改成true、禁用状态改成false怎么办,一个一个找着改吗?而且下一个接你代码的人看着一堆0、1肯定懵逼啊?这啥啊?那就要换成【常量】
根据自己、企业具体业务需求写入你的常量文件
(3)context
网上的解说是 “存放上下文”,那具体上下文是啥?以我的理解,这就是前端Vue里面的 “VueX” 或者 uniapp里的 “uni.setStorageSync”,可以保存各种临时数据信息,并处理它们;
比如我们前端开发时用vueX可以在单独某个页面获取到一些获得的数组、列表数据之后,通过vueX保存到整个项目中,然后在别的页面要用的时候取出,还可以进行增删查改;uni.setStorageSync也是,这种业务最常见的就是在登陆的时候,保存【当前登录的用户信息】,然后在其他地方要用【当前用户信息】的时候再取出来用
那么java后端这里也一样,比如最常有的就是【BaseContext】记录登录的用户信息,那么它的原理其实是用【ThreadLocal 线程变量】,在java当前线程里开辟了一个存储空间,然后当多个其他线程需要访问这个变量,就可以来 “共享空间” 共享这个变量了(至于这个ThreadLocal不是重点我不打算讲,有需要自己去看相关知识点:史上最全ThreadLocal 详解(一)-CSDN博客)
例子:
4、enumeration
存放各种枚举类,老实说我不知道有啥用,暂时没用到......
(4)exception
很有用的一个目录,存放各种【自定义异常类型的类】
那么我们可以留意到,【GlobalException】里基本的一个【全局异常处理器】里,它的参数也就是捕捉的异常类型,叫【BaseException】。
那么回到【common模块】,在exception目录下的这些【自定义异常类型的类】里,最基础的、必须得有的一个就是【BaseException】,它用于继承java异常错误【Exception类】里最最特殊的【RuntimeException】类的异常。
【RuntimeException】是Exception中的一个子类,是由程序逻辑错误引起的异常情况,即在代码编写过程中产生的错误。它为什么特殊?因为【Exception】中除了他所有子类都必须强制性的做出异常处理,也就是我们常见的“爆红”报错;但是【RuntimeException】不是显性的异常报错,它允许用户自定义对他的异常种类进行处理,处理也行、不处理放着不管也行。
那么【BaseException】继承了【RuntimeException】,他代表【全局异常】,在不确定具体什么异常的情况下都可以用它。而这样一来,也就成为【exception】里其他所有【捕捉异常类】的 “爹”(父类),基本所有错误异常都在它的基础上。
那么细心的朋友就会发现,代码里抛出异常并传入【自定义报错信息】的并不是BaseException啊,你看
但是,它们都无一例外是继承了【BaseException】
看到这里,应该很多人已经乱了,能坚持看到这的人都很牛逼了,我当时也是很懵,看着代码跳来跳去研究,终于搞明白了这之间的逻辑,让我用个图画给大家看(可以点击图片放大观看)
(5)json
这里放的是用于将【Java对象与json格式之间相互转换】用的【JacksonObjectMapper】
还是一样,我在之前讲序列化的文章里讲过了这部分,具体文章在下面文章的第三大点:java里的序列化反序列化、HttpMessageConverter、Jackson、消息转化器、对象转化器...都是啥?-CSDN博客
(6)properties
我们之前学javaweb的时候,可能【配置类】都是放在一个叫 “config” 的目录下,但是当时可以留意到这里面有的文件叫 “xxxProperties”,有的叫 “xxxConfig”,【properties】和【config】这两文件其实有不同作用的
所以这里企业项目开发,要把【properties】跟【config】单独分开,其中各种【properties】放到【common模块】的【properties】目录下;【config】放到【service模块】的【config】目录下
那么【properties】跟【config】到底有什么区别?
【properties】目录下的类通常用于封装外部配置文件中的属性,并提供一种方便的方式来访问这些属性。它们通常与业务逻辑或特定服务无关,而只是提供一些全局配置信息,更侧重于配置属性的读取和封装。
这里我当时其实是忘了之前的基础.......其实我写过关于【properties】的文章,其实他就是 “最便捷的spring boot工程配置” :《后端之路——最规范、便捷的spring boot工程配置_springboot 配置文件规范性-CSDN博客》,
它是为了跟连接数据库、配置端口号...配置的那个【application.yml】文件搭配的,用来方便管 理、配置各种【工具所需要的属性、参数】例如:
【config】目录下的类则更倾向于定义特定于应用程序某一部分的配置或行为。可能更侧重于配置web层、Redis数据库连接......等等的实际应用和Bean的创建。不需要搭配【application.yml】配置,基本以后都不会变,扫描的时候会自动被执行)
比如:
这些专业知识到后面再讲,这里只需要知道,这些不需要搭配【application.yml】配置,基本以后都不会变,自己本质就是一个【@Bean对象】可以在外面被【@Autowired】注入使用。(当然除了拦截器配置,拦截器配置不用在外面被【@Autowired】注入使用,扫描的时候会自动被执行)
(7)utils
这个不用我过多解释,很好理解,就是【工具类】,哪里用到它们就调用,常用的工具类就:阿里云oss上传工具、HttpClient后端发送网络请求的接口类、jwt令牌加密解密的工具类、以及一些什么微信支付宝接口类啥的......前三个是最常用的,可以直接cv源代码:
3、service模块
终于来到了最重要最重要的一个模块,所有业务逻辑都是在这处理,那么除了controller、service、mapper三层之外,其他的包基本都是与它们直接相关联所需要的东西,或者可以说跟web层相关的东西。
基本的目录结构是这样:
名称 | 说明 |
---|---|
config | 存放配置类 |
controller | 存放controller类 |
interceptor | 存放拦截器类 |
mapper | 存放mapper接口 |
service | 存放service类 |
SkyApplication | 启动类 |
那么这里有两个类基本要有,一个是【Redis的RedisTemplate配置类】一个是【web层配置类】
(1)config
web层配置类
web层配置类起名可以叫【WebMvcConfiguration】,这里设置的都是【web层的相关配置】,
比如:拦截器类写好之后需要的【注册拦截器配置】来开启拦截器
Swagger自动生成接口文档所需要的【knife4j】的配置
对应将knife4j的Swagger生成的接口文档的静态资源映射(为了让页面渲染显示接口文档数据)
这里提一下一个知识点:
Redis的RedisTemplate
先不说,因为这里好没讲到Redis,看我以后的文章会讲。
(2)controller、service、mapper、intercepter
这几个为什么放一块,因为以及熟悉得不能再熟悉了,学完javaweb的都应该知道这四个是啥了,三层架构加拦截器嘛......
(3)handler
基本有一个GolobalExceptionHandler就够了,这个目录就是一个放全局异常捕获器的目录。
这些全局捕获器可以自动捕捉到报错,然后根据自己的喜好编写报错时要错的事逻辑
(4)xxxApplication
xxx是你的项目名字,通常这样命名xxxApplication,这就是你整个项目的启动类,加上【@SpringBootApplication】和【@EnableTransctionManagement】才可以变成启动类,整个项目就以他为入口
(5)最后一个,resourse目录
都知道resourse是放静态资源的地方,那么我们一般就放【动态sql—mapper包】、【application.yml】、【application-dev.yml】
【动态sql—mapper包】没什么好说的,就是xml文件的mapper层文件,可以动态操作sql
那为啥会有两个yml配置文件??
因为写过后端项目的朋友一定会有这样的经历,如果你要将这个项目在自己的本地环境运行、然后通过局域网跟别人联调、然后放到服务器联调,那么数据库、网络监听的地址、端口号这些肯定都是不一样的,你需要重复“N的N次方次”、“一万一亿次”的“无限循环”地进行这些配置数据的修改。
那么在公司是绝对不允许这么低效率开发的,因为企业开发规定了开发有三个阶段:【开发环境dev】、【测试环境test】、【生产环境prod】
那么我这里个人开发学习没那么复杂,就先创建一个【开发环境dev】的【aaplication-dev.yml】,然后在里面写上真实的数据值
然后在基础的真正的那个【application.yml】里,首先在spring.profiles.active指定要用的是哪个环境,注意对应环境的yml文件必须得写成【application-xxx.yml】,这样才能直接根据【xxx】来查找定位到是哪一个环境的yml文件。
然后通过【变量】的形式来引入【aaplication-dev.yml】的真实数据值,使用的语法:【${ xxx.xxx.xxx }】