java-web
说明java-web
前后端分离java-web
的开发生命周期HTML
+ CSS
实现静态页面JavaScript
动态渲染页面Vue
+ Element/Iview
急速前端页面开发Spring
框架SpringMVC
框架Mybatis/JPA
框架SpringBoot
框架java-web
说明
web
的概念摘自百度百科,阐述如下:
World Wide Web
,简称web
,意思是:全球广域网,我们也称之为万维网。它是一种基于超文本和HTTP的、全球性的、动态交互的、跨平台的分布式图形信息系统。
简单来说,web
就是一个网站。通过在浏览器地址栏输入指向该网站的URL,我们可以从该网站获取或上传相关的资源。如:淘宝/京东/知乎/微博等等的官网都可以认为是一个网站。
java-web
项目,就是以主体编程语言是java
,利用其他各种编程语言实现的辅助中间件,实现的网站项目。
java-web
前后端分离
前后端分离已经是互联网项目开发的业界标准使用方式。
其核心思想是:前端HTML
页面通过AJAX
调用后端的Restful API
接口,并统一使用JSON
格式进行数据的交互。
把一个web
项目切分成前端项目和后端项目的好处:
后端java工程师对服务的追求是:高并发、高可用、高性能。
这样后端java工程师就可以把更多的精力放在学习java基础,设计模式,jvm原理,spring+springmvc原理及源码,linux,mysql事务隔离与锁机制,http/tcp,多线程,分布式架构,弹性计算架构,微服务架构,java性能优化等等方面。
前端工程师对服务的追求是:页面表现,速度流畅,兼容性。
这样前端工程师就可以把更多的精力放在学习html5,css3,jquery,angularjs,bootstrap,reactjs,vuejs,webpack,less/sass,gulp,nodejs,Google V8引擎,javascript多线程,模块化,面向切面编程,设计模式,浏览器兼容性,性能优化等等方面。
在开需求会议时,由前端工程师和后端工程师商议并统计要实现的接口。需要按照统一的标准,将每个接口的请求参数、方法、路径以及返回值都确定下来,并汇总成最终的接口文档。
前端工程师只需要关注页面以及路由的实现,后端工程师只需要关注接口实现以及性能优化。两者可以基于接口文档并行开发。而且这种开发方式在后期测试以及上线后,更有利于问题的排查定位。
前端服务器使用
nginx
,后端服务器使用tomcat
。前端的css/js/图片等静态资源可以放在特定的文件服务器上(如阿里的oss服务器)并使用cdn加速。然后,除接口以外的所有http请求全部会转移到nginx上,能够极大地减少后端服务器的压力。后端服务也可以按数据库、缓存、消息队列、应用切割等方式进行拆分并发布到不同的服务器上,能够降低单个节点的服务器宕机造成的影响。
按我自己的理解,前后端分离之后,
java-web
的开发生命周期
我理解的web开发迭代流程图如下:
实际的开发过程可能不会这么规范,但java-web项目的开发迭代生命周期,基本都是按照这个流程进行的。
HTML
+ CSS
实现静态页面
Hyper Text Mark-up Language
,超文本标记语言,简写为HTML。Cascading Style Sheet
,层叠样式表,简写为CSS。html
和css
是网页的基础。html
包含的是一系列统一规定的标准标签,用来定义网页的基础布局。如我们最常见的元素:块级元素标签和行内元素
标签。
css
就是为了将页面内容与页面表现区分开来的,通过选中html
中的某块内容来按某种要求显示。其中最重要的是要理解盒子模型
,选择器
和部分样式定义方法
。
简单用实际的案例说明如下:
新建一个文件1.html
并编辑内容如下:
我是块级元素div,我会独占一行
-------我是行内元素span1,可与其他行内元素共同占用1行,长度自适应并会自动换行-------
-------我是行内元素span2,可与其他行内元素共同占用1行,长度自适应并会自动换行-------
-------我是行内元素span3,可与其他行内元素共同占用1行,长度自适应并会自动换行-------
将该文件在浏览器中打开的效果如下:
如上图所示:
是块级元素,其中的内容会独占一行。
是行内元素,多个行内元素会共占1行,并且其长度自适应并会自动换行。
- 行内元素span2定义了属性id值为test,通过css的id选择器选中该元素并设置了字体大小为18px,字体颜色为红色。
2.1.2 JavaScript
动态渲染页面
JavaScript
是一门脚本编程语言。
这里简单谈一下我对JavaScript
的理解。它在浏览器主要做的事情有:监听浏览器网页的各种事件,基于各种事件的回调发起http请求
或者直接动态渲染DOM
(html
中的某个元素)。
简单用实际的案例说明如下:
新建一个文件2.html
并编辑内容如下:
<html>
<body>
<button onClick="toRed()">变红button>
<button onClick="toBlue()">变蓝button>
<p id="test">未点击前的DOMp>
body>
<script>
function toRed() {
document.getElementById('test').innerHTML = "点击了变红按钮"
}
function toBlue() {
document.getElementById('test').innerHTML = "点击了变蓝按钮"
}
script>
html>
将该文件在浏览器中打开的效果如下:
如上图所示:
- 页面中有两个按钮
,
。这两个按钮分别绑定了两个不同的点击事件。
- 变红按钮绑定的点击回调事件是
toRed()
,其处理内容是:通过全局文档对象document
查找到id为test
的dom
元素,并将其内部的html
修改为“点击了变红按钮”。
- 变蓝按钮绑定的点击回调事件是
toBlue()
,其处理内容是:通过全局文档对象document
查找到id为test
的dom
元素,并将其内部的html
修改为“点击了变蓝按钮”。
使用javascript
在浏览器/客户端编程(CS/BS中的Client/Browser端)时,我们能够监听用户操作触发的事件,基于预先定义好的事件回调处理,动态地修改页面的内容和样式。
javascript
中最重要的一个技术点是AJAX
。客户端可以使用AJAX
技术向服务端发送HTTP
请求。
相关的基础代码如下:
// 创建XMLHttpRequest对象
var request = new XMLHttpRequest();
// 当http请求的状态发生变化时,会调用onreadystatechange()方法
request.onreadystatechange = function(){
// 当readyStatue = 4且status = 200时,表示http请求成功完成
if (request.readyState === 4 && request.status === 200) {
// 获取http请求返回的字符串类型的响应文本
var response = request.responseText;
// 通过json解析后,将响应文本的值填充到页面对应的元素中
var jsonObj = JSON.parse(response);
// todo 将响应文本解析之后的内容填充到页面中
}
}
// 规定http请求的的类型、路径、是否异步
request.open('method_type','url',async);
// 如果是post请求提交表单数据,需要设置请求头
request.setRequestHeader("Content-type","application/x-www-form-urlencoded");
// 发送http请求
request.send();
简单说明一下:
我们可以利用XMLHttpRequest
对象实例发送HTTP
请求。其中主要用到该实例的三个方法:open()
,send()
和onreadystatechange()
。
open()
用来指定发起HTTP
请求的方法类型、路径和是否异步。
send()
用来真正发送HTTP
请求。
onreadystatechange()
,当该HTTP
请求的状态发生改变时,会回调该方法。我们可以利用该实例判断服务器是否正确处理该请求(状态码为200时),如果正确响应则可将返回值解析后填充到当前页面的某个标签中。
2.1.3 Vue
+ Element/Iview
急速前端页面开发
当前主流的三大前端开发框架是:React
,Vue
,Angular
。其中最好入门的就是Vue
框架。熟读一遍Vue官方文档,理解它的这种数据与页面双向绑定
的概念,实际上手写几个前端小项目就可入门。
Element
和Iview
是两个非常常用的UI组件库。在项目中引入相关依赖包后,可以直接使用相关组件快速进行前端页面的开发。
ElementUI
的官方文档详见element中文官方文档。
IviewUI
的官方文档详见iview中文官方文档。
简单使用ElementUI
提供的组件,查看其常用组件的样式效果如下:
新建一个文件3.html
并编辑内容如下:
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
head>
<body>
<div id="app">
<el-input placeholder="Element输入框">el-input>
<el-divider>Element分割线el-divider>
<el-button type="primary" @click="visible = true">Element按钮el-button>
<el-dialog :visible.sync="visible" title="Element对话框">
<p>对话框内部内容p>
el-dialog>
div>
body>
<script src="https://unpkg.com/vue/dist/vue.js">script>
<script src="https://unpkg.com/element-ui/lib/index.js">script>
<script>
new Vue({
el: '#app',
data: function() {
return { visible: false }
}
})
script>
html>
将该文件在浏览器中打开的效果如下:
2.2 后端技术栈
2.2.1 Spring
框架
基于百度百科查看可知:Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
控制反转:Inversion of Control,简单来说就是把对象生命周期的管理交给Spring容器。诸如:数据库连接池对象、Redis连接池对象、RabitMQ连接池对象、Quartz定时任务调度对象等等,这些“大”对象的创建和销毁是一个很损耗资源的操作。我们可以通过XML
和JavaConfig
的方式把这些对象交给Spring容器管理。如果需要使用时,通过依赖注入(DI:Dependency Injection)的方式拿到这些对象的实例即可。这些对象的创建和回收都会有Spring容器帮我们处理。注:Spring容器管理的对象都是单例的。
下面以Spring容器加载quartz相关对象的实例
做出说明:
- 首先在
pom.xml
文件中导入必要依赖如下:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>4.3.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>4.3.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.quartz-schedulergroupId>
<artifactId>quartzartifactId>
<version>2.2.3version>
dependency>
- 运行定义打印当前时间的工作类
TimePrinterJob
内容如下:
public class TimePrinterJob {
private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
public void printTime() {
System.out.println("测试spring-xml配置quartz定时任务 TimePrinterJob.printTime() " + sdf.format(new Date()));
}
}
- 以XML方式使Spring容器管理quartz相关的实例bean,配置文件
spring-quartz.xml
内容如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="timePrinterJob" class="com.netopstec.spring_quartz.TimePrinterJob"/>
<bean id="printTimeJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="timePrinterJob"/>
<property name="targetMethod" value="printTime"/>
bean>
<bean id="printTimeTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="printTimeJobDetail"/>
<property name="cronExpression" value="/3 * * * * ?"/>
bean>
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="printTimeTrigger"/>
list>
property>
bean>
beans>
上述相关源码详见我的github
说明如下:
这里主要查看的是spring-quartz.xml
的内容。很明显地能看到,Spring管理的quartz相关的实例,有三层包裹关系。
printTimeJobDetail
指定了要交由quartz管理任务对应的方法即TimePrinterJob.printTime()
。
printTimeTrigger
指定了该任务的触发器即执行时机为“每3秒执行一次”。
scheduler
指定了要启用的触发器列表。
此时,业务逻辑比较简单,我们还能够迅速地梳理这些业务对象实例的关联关系。当随着业务的拓展,业务之间的耦合关联关系越来越复杂,使用Spring容器帮我们管理这些业务对象实例就很有必要了。
面向切面:Aspect Orient Programming,简单来说,将正常的业务流程当作一个维度,要新增的功能与业务无关,所以放在另外一个维度(切面)里面进行处理实现。
如日志、全局异常处理等相关功能与业务功能无关,此时就需要用到面向切面编程。很明显地,面向切面编程的核心原理是动态代理。Java支持使用jdk
或cglib
来实现动态代理,相关的内容这里就不再赘述。
这里就简单介绍一下面向切面编程的相关概念和一个SpringBoot简单切面编程案例
。
- 切面:
Aspect
,整个额外功能的整体;
- 切点:
PointCut
,要添加额外功能对应于正常业务流程所在维度的位置;
- 连接点:
JointPoint
,通过连接点我们可以获取正常业务流程中的相关信息。
- 通知:
Adivce
,有如下类型的通知:前置通知before、后置通知after,环绕通知around,异常通知AfterThrowing等等。
一个SpringBoot简单切面编程使用案例
如下:
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.netopstec.test.service.impl..*.*(..)")
private void pointCut(){}
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("调用业务方法之前,可以执行添加一些其他的功能");
// 实际调用方法
Object[] args = joinPoint.getArgs();
Object proceed = joinPoint.proceed(args);
System.out.println("调用业务方法之后,可以执行添加一些其他的功能");
return proceed;
}
}
上面这个小案例,介绍的是功能最强大的通知—环绕通知。它是扫描com.netopstec.test.service.impl
下的所有业务方法,在系统执行业务方法joinPoint.proceed(args)
的前后,添加自定义额外功能(这里只是在控制台打印两句话,可以根据实际情况添加一些自己想要的额外功能)。
2.2.2 SpringMVC
框架
Spring Web MVC
是一种基于Java的实现了Web MVC
设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦。请求驱动,就是请求-响应这种设计模型。
相关的概念解释如下:
- 模型,
Model
指的是封装了应用程序数据的POJO。
- 视图,
View
指的是负责呈现模型数据的HTML页面。
- 控制器,
Controller
负责处理用户的请求,处理数据并生成相应的模型,然后把生成好的模型传递给相应的视图解析器,由视图解析器解析生成相关的HTML页面。
SpringMVC执行流程图如下:
实际SpringMVC处理请求,主要是查看DispatcherServlet.doDispatch()
源码的处理,先截取其部分关键源码如下:
public class DispatcherServlet extends FrameworkServlet {
// ...
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
// 基于请求request获取相应的处理执行链(责任链)
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 获取相应的处理适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
// handle方法内部是调用实际的业务处理方法(动态代理)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 获取对应的视图名称
this.applyDefaultViewName(processedRequest, mv);
// 处理拦截器定义的业务
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
// 加载模型,渲染视图
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}
// ...
}
基于源码和执行流程图,详解流程步骤如下:
- 客户端或浏览器发起请求到前端控制器
DispatcherServlet
- 前端控制器请求处理器映射器
HandlerMapping
,查找出相应的处理执行链对象HandlerExecutionChain
项目启动加载时会初始化处理器映射器。它是一个包含所有业务对应的处理执行链的容器((一个处理执行链肯定会包含如业务标识URI、业务方法名、业务拦截器列表等信息)。这个处理执行链对象可能会包含多个HandlerInterceptor
拦截器对象。真正执行业务方法时,会逐步执行拦截器。这是典型的责任链设计模式,处理器执行对象拦截器对象可以自行添加。
- 前端控制器基于处理执行链对象
HandlerExecutionChain
,获取相应的处理器适配器对象HandlerAdapter
- 基于处理器适配器
HandlerAdapter.handle()
去执行真正的业务方法,并返回模型视图对象ModelAndView
(业务方法的执行,这里很明显使用的就是动态代理设计模式。ModelAndView
是springmvc框架的一个底层对象,包括Model
和view
)
- 前端控制器将模型视图对象
ModelAndView
传递给视图解析器,由视图解析器进行视图解析 (根据逻辑视图名解析成真正的视图(jsp))并返回视图View
,通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可
- 前端控制器进行视图渲染 (视图渲染将模型数据(在
ModelAndView
对象中)填充到request域)后,响应用户。
实际测试SpringMVC进行业务解耦
的源码内容如下(可以自行debug测试,核对SpringMVC执行流程):
- 首先在
pom.xml
文件中导入必要依赖如下:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.3.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.8version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>[2.9.10.1,)version>
dependency>
- 除了设置
web.xml
的内容,还需要在spring-mvc-servlet.xml
中定义视图解析器并开启注解驱动;
<beans>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/jsp/value>
property>
<property name="suffix">
<value>.jspvalue>
property>
bean>
beans>
- 基于视图解析器的配置,在目录
/WEB-INF/jsp/
下定义视图hello.jsp
如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
Title
姓名:${user.name}
花名:${user.nick}
年龄:${user.age}
爱好:${user.hobby}
- 定义好业务方法如下:
@Controller
@RequestMapping("/users")
public class UserPageController {
@RequestMapping("/page")
public ModelAndView showPage(){
User user = new User();
user.setName("戴振焱");
user.setNick("zhenye");
user.setAge(27);
user.setHobby("动漫、炉石");
ModelAndView modelAndView = new ModelAndView();
// 处理并生成相应的模型user
modelAndView.addObject(user);
//指定视图
modelAndView.setViewName("hello");
return modelAndView;
}
}
@RestController
@RequestMapping("/users")
public class UserJsonController {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@GetMapping(value = "/json", produces="text/html;charset=UTF-8")
public String showJson() {
User user = new User();
user.setName("戴振焱");
user.setNick("zhenye");
user.setAge(27);
user.setHobby("动漫、炉石");
String userInfo = null;
try {
userInfo = OBJECT_MAPPER.writeValueAsString(user);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return userInfo;
}
}
由于开启了注解驱动
,Spring容器会自动检测所有的类注解@Controller
和@RestController
。
@Controller
表示需要使用视图解析器ViewResolver
,其所有方法的返回值都是映射/WEB-INF/jsp/
目录下查找所有的视图(如这里的viewName
是hello
,就是去找视图 /WEB-INF/jsp/
+ hello
+ .jsp
)。
@RestController
则表示不使用视图解析器,直接给用户返回相应格式的数据(这里的json字符串)。
- 打
war
包并部署到tomcat容器中后,测试接口的效果图如下:
上述相关源码详见我的github
2.2.3 Mybatis/JPA
框架
Mybatis
和JPA
是两种ORM
(Object/Relational Mapping
,对象关系映射)的框架。简单来说,这两个框架来维护java-bean与数据表的关系,开发人员只需关注业务逻辑本身。
这两个框架各有优缺点。
JPA
,Java Persistence API
及Java持久层API。它是一种java持久层的实现规范。引入该框架后,开发人员可以不用写SQL语句,利用该框架提供的API直接操作数据库表。
Mybatis
,其前身是ibatis
。它支持定制化 SQL、存储过程以及高级映射。
这两个框架优劣势及其明显。如果项目业务简单且后期扩展功能较少,推荐选用JPA
。绝大部分业务都是单表操作,使用该框架可以极大地提升开发效率。否则最好选用Mybatis
框架,其支持编写原生SQL语句,更加灵活,后期扩展功能时更易实现。
以Mybatis
框架为例,简单介绍如何使用这些ORM框架。
- 首先,在
pom.xml
文件中导入必须的依赖如下:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>4.3.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>6.0.6version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.8version>
dependency>
dependencies>
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
resources>
build>
- 新建数据库表以及该表对应的实体类
建库建表语句如下:
CREATE DATABASE IF NOT EXISTS easy-mybatis;
USE easy-mybatis;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL,
`nick` varchar(32) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`hobby` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
user
表对应的实体类如下:
@Getter
@Setter
@ToString
public class User {
private Long id;
private String name;
private String nick;
private Integer age;
private String hobby;
}
- 编辑Mybatis的配置文件
mybatis-config.xml
的内容如下:
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/easy-mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<package name="com/netopstec/easy_mybatis/mapper"/>
mappers>
configuration>
- 编辑接口以及对应的映射XMl文件内容如下:
接口层代码如下:
public interface UserMapper {
List<User> selectAll();
int insertOne(User user);
}
对应映射文件内容如下:
<mapper namespace="com.netopstec.easy_mybatis.mapper.UserMapper">
<select id="selectAll" resultType="com.netopstec.easy_mybatis.entity.User">
SELECT * FROM `user`
select>
<insert id="insertOne" parameterType="com.netopstec.easy_mybatis.entity.User">
INSERT INTO `user` (id, name, nick, age, hobby)
VALUES (#{id}, #{name}, #{nick}, #{age}, #{hobby})
insert>
mapper>
- 测试代码如下:
public class MybatisTest {
public static void main(String[] args) throws IOException {
InputStream reader = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();
System.out.println("新增之前,user表数据:" + userList);
User user = new User();
user.setName("戴振焱");
user.setNick("zhenye");
user.setAge(27);
user.setHobby("动漫,炉石");
userMapper.insertOne(user);
userList = userMapper.selectAll();
System.out.println("新增之后,user表数据:" + userList);
}
}
最终的测试效果图如下:
上述相关源码详见我的github
2.2.4 SpringBoot
框架
SpringBoot
框架的核心思想是:“约定优于配置”。
整合一个基于Java-Spring
开发处理的项目,一般都需要进行非常多的额外配置。如上面需要使用Mybatis
框架时,需要新建并完善mybatis-config.xml
配置文件中的内容。这对于对该框架或其配置项不熟的新手来说,非常的不友好。
SpringBoot
就对一些常用的第三方工具包的常用配置进行了整合。基于SpringBoot
框架开发项目,当导入SpringBoot整合后提供的依赖包(官方项目常常以spring-boot-starter
开头)后,它会为我们开发的项目添加一下默认的配置。如上面的数据源的配置,我们可以导入spring-boot-starter-jdbc
对应的依赖包,在配置文件中添加必要的四个配置(spring.datasource.url
,spring.datasource.driver-class-name
,spring.datasource.username
,spring.datasource.password
)即可使用。
当前SpringBoot版本支持自动配置的第三方工具参考源码spring.factories
内容,如下图:
现以开发一个自定义的starter项目为例,进行说明。
假设有一个日常作息时间表如下:
默认时间节点
事项
对应配置项字段
08:00
起床时间/睡觉结束时间
sleepTimeEnd
08:30
早餐开始时间
breakfastTimeStart
09:00
早餐结束时间
breakfastTimeEnd
12:00
午餐开始时间
luntchTimeStart
12:30
午餐结束时间
luntchTimeEnd
19:00
晚餐开始时间
dinnerTimeStart
19:30
晚餐结束时间
dinnerTimeEnd
00:00
睡觉开始时间
sleepTimeStart
现在开发一个相应项目plan-spring-boot-starter
,需要该项目提供如下功能:
formatPlanList()
,打印现在的作息时间表。
doing(Date time)
,确认按照作息时间,该时间点正在做什么。
nextToDo(Date time)
,确认按照作息时间,该时间点下一步要做的什么。
- 所有事项对应的时间节点都可以自行配置。
新建一个maven
项目—plan-spring-boot-starter
,其最终目录如下:
- 首先,在
pom.xml
文件中导入spring-boot自动配置实现必要的依赖包如下:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-autoconfigureartifactId>
<version>1.5.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<version>1.5.2.RELEASEversion>
<optional>trueoptional>
dependency>
- 然后,编辑可自动配置的配置类
PlanProperties
如下:
@Getter
@Setter
@ConfigurationProperties(prefix = "spring.plan")
public class PlanProperties {
/**
* 起床时间/睡觉结束时间
*/
private String sleepTimeEnd = "08:00";
/**
* 早餐开始时间
*/
private String breakfastTimeStart = "08:30";
/**
* 早餐结束时间
*/
private String breakfastTimeEnd = "09:00";
/**
* 午餐开始时间
*/
private String lunchTimeStart = "12:00";
/**
* 午餐结束时间
*/
private String lunchTimeEnd = "12:30";
/**
* 晚餐开始时间
*/
private String dinnerTimeStart = "19:00";
/**
* 晚餐结束时间
*/
private String dinnerTimeEnd = "19:30";
/**
* 睡觉开始时间
*/
private String sleepTimeStart = "00:00";
}
- 然后,编写实现了上述三个功能(打印现在的作息时间表
formatPlanList()
,确认当前正在做什么doing(Date time)
,确认下一步要做什么nextToDo(Date time)
)的业务功能类PlanService
如下:
public class PlanService {
private PlanProperties planProperties;
public PlanService(){}
public PlanService(PlanProperties planProperties) {
this.planProperties = planProperties;
}
public String formatPlanList() {
// ... //
return plan;
}
public String doing(Date date) {
// ... //
return doingThing;
}
public String nextTodo(Date date) {
// ... //
return nextTodo;
}
}
- 编辑由Spring容器帮我们管理的自动配置类
PlanServiceAutoConfiguration
如下:
@Configuration
@EnableConfigurationProperties(PlanProperties.class)
@ConditionalOnClass(PlanService.class)
@ConditionalOnProperty(prefix = "spring.plan", value = "enabled", matchIfMissing = true)
public class PlanServiceAutoConfiguration {
@Autowired
private PlanProperties properties;
@Bean
@ConditionalOnMissingBean(PlanService.class)
public PlanService planService(){
PlanService planService = new PlanService(properties);
return planService;
}
}
- 最后,确认让SpringBoot加载自动配置类,编辑文件
spring.factories
如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.netopstec.plan.PlanServiceAutoConfiguration
执行mvn clean install
命令,对该自动配置项目进行打包即可。
新建一个测试该自动配置的SpringBoot项目—test-for-plan
,然后在pom.xml
导入上述自动配置依赖包如下图:
启动该测试项目,执行测试代码效果图如下:
很明显地能够看到,注入业务功能类PlanService
后,可以直接使用该业务功能类的各项功能方法。而且对应的配置项就是默认值。
在SpringBoot配置文件,修改这些配置项的内容(如:将早餐结束时间修改为"08:45",午餐结束时间修改为"12:15"),如下:
再次执行测试方法,效果如下:
很明显地看到,这个项目实现了自动配置的功能。而且也符合实际使用自动配置的逻辑。在项目配置文件中中新加了配置项内容后,其内容会覆盖配置项的默认值。
上述相关源码详见:
- 自动配置项目plan-spring-boot-starter
- 自动配置测试项目test-for-plan