本章节通过对公告业务对象的分析,进行业务逻辑操作的具体设计和实现。
说明,在这张图中我们从右侧向左看,最右边是数据层,中间是业务层,最左边是对业务层的测试。
定义通知业务逻辑接口及相关方法,关键代码如下:
package com.cy.pj.sys.service;
import com.cy.pj.sys.pojo.SysNotice;
import java.util.List;
/**
* 此对象为通告业务逻辑对象负责对通告业务做具体落地实现
* 1)调用数据逻辑对象执行数据操作
* 2)日志记录
* 3)事务处理
* 4)缓存
* 5)权限
* 6).............
*/
public interface SysNoticeService {
/**
* 新增通告信息
* @param notice
* @return
*/
int saveNotice(SysNotice notice);
/**
* 基于条件查询通告信息
* @param notice
* @return
*/
List<SysNotice> selectNotices(SysNotice notice);
/**
* 基于通告删除通告信息
* @param ids
* @return
*/
int deleteById(Long… ids);
/**
* 基于id查询某条通告
* @param id
* @return
*/
SysNotice selectById(Long id);
/**
* 更新通告信息
* @param notice
* @return
*/
int updateNotice(SysNotice notice);
}
日志是进行数据分析,异常分析的一种有效手段,我们在业务执行过程中对业务相关操作,进行日志记录是必须的一道程序。在进行日志的记录是,日志API选型也会影响到后续日志操作的可维护性和可扩展性。本次业务日志我们选择SLF4J,这是一套Java中基于门面模式而设计的一套日志API规范,其关键应用如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**创建日志门面API对象*/
private static final Logger log=
//通过工厂创建日志对象
LoggerFactory.getLogger(SysNoticeServiceImpl.class);
其中,SLF4J基于此标准的实现有logback,log4j等具体的实现,我们编程时,建议耦合与规范或标准进行日志记录。
定义通知业务逻辑接口的实现类,并重写业务方法,重写过程中对相关业务进行日志记录,关键代码如下:
package com.cy.pj.sys.service.impl;
import com.cy.pj.sys.dao.SysNoticeDao;
import com.cy.pj.sys.pojo.SysNotice;
import com.cy.pj.sys.service.SysNoticeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Service注解由spring提供,一般用于描述分层架构中的业务逻辑对象,这样的类
* 会交给spring管理
*/
@Service
public class SysNoticeServiceImpl implements SysNoticeService {
/**创建日志门面API对象*/
private static final Logger log=
//通过工厂创建日志对象
LoggerFactory.getLogger(SysNoticeServiceImpl.class);
@Autowired
private SysNoticeDao sysNoticeDao;
//重写方法的生成 (选中类,然后alt+enter)
@Override
public int saveNotice(SysNotice notice) {
int rows=sysNoticeDao.insertNotice(notice);
return rows;
}
@Override
public List<SysNotice> selectNotices(SysNotice notice) {
//System.out.println(
"system.out.start:"+System.currentTimeMillis());
log.debug("start: {}",System.currentTimeMillis());
//这里的{}表示占位符号
List<SysNotice> list=sysNoticeDao.selectNotices(notice);
log.debug("end: {}",System.currentTimeMillis());
return list;
}
@Override
public int deleteById(Long... ids) {
int rows= sysNoticeDao.deleteById(ids);
return rows;
}
@Override
public SysNotice selectById(Long id) {
SysNotice notice=sysNoticeDao.selectById(id);
return notice;
}
@Override
public int updateNotice(SysNotice notice) {
int rows=sysNoticeDao.updateNotice(notice);
return rows;
}
}
定义单元测试类,对业务方法进行单元测试
package com.cy.pj.sys.service;
import com.cy.pj.sys.pojo.SysNotice;
import org.junit.jupiter.api.Test;
@SpringBootTest
public class SysNoticeServiceTests {
@Autowired
private SysNoticeService sysNoticeService;
@Test void testFindNotices(){
SysNotice notice=new SysNotice();
notice.setType("1");
notice.setModifiedUser("tony");
sysNoticeService.selectNotices(notice);
}
}
分层架构中service是如何定义的?
公告业务接口及方法的设计
公告业务实现与公告数据层接口的关系
@Service注解是谁定义的,有什么作用?
为什么要记录日志,一般都会记录什么信息?
@Service描述的类中是如何应用日志API的?
你知道哪些日志级别,在哪里进行配置?
日志要写到文件的基本配置是什么?
….
NoSuchBeanDefinitionException
NullPointerException
Spring MVC是MVC设计思想在Spring框架中的一种实现,基于这样的设计思想,Spring框架设计了一些相关对象,最后将这些对象组装在一起,构建了一个专门用于处理Web请求的模块,称之为spring web模块,这个模块底层基于MVC设计思想,封装了对Servlet的技术的应用,简化了程序员对请求和响应过程中数据的处理,其简易架构分析如图所示:
其中,核心组件分析:
DispatcherServlet :前端控制器, 请求处理入口(你去银行营业厅有一个大堂经理负责问你需求一样,你去公司有前台接待一样)。
HandlerMapping:映射器对象, 管理url与controller的映射关系(就类型于基于身份证号能够查到对应人的信息一样)。
Interceptors:拦截器,实现请求响应的共性处理(类似与你进大楼要看你的健康码,你上地铁要进行安检一样)。
Controller:后端控制器-handler, 负责处理请求的控制逻辑(银行用于处理各种请求的业务的窗口)。
ViewResolver:视图解析器,解析对应的视图(类似别人给了你一个地址,你知道这个地址再哪,有人帮你指路)。
备注:先鸟瞰其全貌,然后基于实践逐步进行渗透、学习每个组件的应用原理。
客户端向服务端发送一个请求,服务端响应一个字符串到客户端,页面上呈现hello spring web mvc。
第一步:创建maven项目模块,名字为04-mvc 其初始pom.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<groupId>com.cy</groupId>
<artifactId>04-mvc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>04-mvc</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
打开pom.xml文件并添加Spring Web依赖(提供了spring mvc依赖支持),代码如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
其中,这个依赖添加时,会关联下载一个tomcat依赖(这个tomcat为一个嵌入式tomcat服务器)
创建项目配置文件application.properties并配置服务启动端口为80(默认为8080)server.port=80 #以application.properties为例
package com.jt;
@SpringBootApplication
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
创建一个客户端请求处理器对象,并将此对象交给spring管理,代码如下:
package com.jt.demo.controller;
@RestController
public class HelloController {
@RequestMapping("/doSayHello")
public String doSayHello(){
return "hello spring web mvc";
}
}
其中:
@RestController注解描述的类为spring web模块的请求处理类型,此类型的对象会交给spring管理。
@RequestMapping注解描述的方法为请求处理方法,此注解内容定义的字符串为访问此方法需要的请求url。
打开浏览器,在浏览器地址栏输入http://localhost/doSayHello,检测日志输出。
创建一个请求参数处理器,例如:
package com.jt.demo.controller;
@RestController
@RequestMapping("/param")
public class ParameterController {
}
在ParameterController中添加方法,基于直接量方式接受客户端请求参数,例如:
//访问方式:http://localhost/param?id=10
//@RequestParam 注解描述方法参数时,默认必须为id参数传值
@RequestMapping
public String doParam01(@RequestParam String id){
return "request params id is "+id;
}
//访问方式:http://localhost/param/rest/10/jack
//{id}{name}表示占位符,用于接收请求url中/rest/后面的内容,注意顺序
//@PathVariable描述方法参数时,表示接收与参数名相同的/rest/{id}/{name}表达式中{id} {name}的值
@RequestMapping("/rest/{id}/{name}")
public String doParam02(@PathVariable Integer id,@PathVariable String name){
return "request params id is"+id+" ; name is "+name;
}
第一步:定义pojo对象,用于接受客户端请求参数,例如:
package com.jt.pojo;
public class ParameterWrapper {
private Integer id;
private String name;
//自己添加set/get/toString方法
}
第二步:定义请求处理方法
//访问方式:http://localhost/param/pojo?id=10&name=jack
//系统底层在使用pojo对象接收请求参数时,底层会通过反射构建参数对象,
//并通过反射调用参数名对应的pojo对象set方法将值封装到pojo对象。
@RequestMapping("/pojo")
public String doParam03(ParameterWrapper parameterWrapper ){
return "request params id is "+parameterWrapper;
}
//访问方式:基于ajax或postman发送post请求,参数类型为application/json,传递数据为json格式:{"id":10,"name":"jack"}
//@RequestBody描述pojo对象时表示参数的值类来自自一个json格式数据
@RequestMapping("/pojo")
public String doParam04(@RequestBody ParameterWrapper parameterWrapper ){
return "request params id is "+parameterWrapper;
}
有时候为了简单,我们不想使用pojo对象接收请求参数,还可以使用map对象来接收,又该如何实现呢?
定义Controller方法,基于map对象接收请求参数,例如:
//访问方式:http://localhost/param/map?id=10&name=jack
//注意:当使用map对象接收请求参数时,必须实用@RequestParam注解对参数进行修饰,
//也可以实用@RequestBody进行描述接收json格式数据
@RequestMapping("/map")
public String doParam05(@RequestParam Map<String,Object> param){
return "request params "+param.toString();
}
定义请求处理器,重点演示响应数据的封装,例如:
package com.jt.controller;
@RestController
@RequestMapping("/resp/")
public class ResponseController {
}
实际项目中我们通常会定义一个pojo对象,封装响应数据(状态吗,消息,业务数据),例如:
public class JsonResult {
/**状态码*/
private Integer state=1;//1 表示OK,0表示Error
/**状态码信息*/
private String message="ok";
/**封装正确的查询结果*/
private Object data;
public JsonResult(){}
public JsonResult(String message){
this.message=message;
}
public JsonResult(Integer state,String message){
this(message);
this.state=state;
}
public JsonResult(Object data){//new JsonResult(list)
this.data=data;
}
//当出现异常时,可以通过此构造方法对异常信息进行封装
public JsonResult(Throwable exception){//new JsonResult(exception);
this(0,exception.getMessage());
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
案例1:在ResponseController中添加请求处理方法,将响应对象封装为pojo对象,例如
//访问方式 http://localhost/resp/pojo
//将响应数据封装为pojo对象,然后spring底层会将这个对象转换为json格式字符串
@RequestMapping("/pojo)
public ResponseResult doResp01(){
Map<String,Object> map=new HashMap<>();
map.put("id",101);
map.put("title","spring mvc");
JsonResult rs=new JsonResult();
rs.setState(1);
rs.setMessage("OK");
rs.setData(map);
return rs;
}
案例2:直接基于response对象响应数据
//访问方式:http://localhost/resp/print
@RequestMapping("/print")
public void doPrint(HttpServletResponse response)throws Exception{
Map<String,Object> map=new HashMap<>();
map.put("id",101);
map.put("title","hello spring web mvc");
//将map中的数据转换为json格式字符串
ObjectMapper om=new ObjectMapper();
String jsonStr=om.writeValueAsString(map);
System.out.println("jsonStr="+jsonStr);
//将字符串响应到客户端
//设置响应数据的编码
response.setCharacterEncoding("utf-8");
//告诉客户端,要向它响应的数据类型为application/json,编码为utf-8.请以这种编码进行数据呈现
response.setContentType("application/json;charset=utf-8");
PrintWriter pw=response.getWriter();
pw.println(jsonStr);
pw.flush();
}
Spring框架中web模块定义了一套全局异常处理规范,基于这个套规范处理Controller中抛出的异常,例如:
package com.jt.advice;
/** @RestControllerAdvice 注解描述的类为全局异常处理类,启动时会交给spring管理*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log=LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**@ExceptionHandler注解描述的方法为异常处理方法,注解中定义的异常类型为方法可以处理的异常类型.*/
@ExceptionHandler(RuntimeException.class)
public JsonResult doHandleRuntimeException(RuntimeException e){
e.printStackTrace();
log.error("exception msg is {}",e.getMessage());
return new JsonResult(e);
}
//可以定义多个异常处理方法
}
Spring Web中的拦截器(Interceptor)基于回调机制,可以在目标方法执行之前,先进行业务检测,满足条件则放行,不满足条件则进行拦截,拦截器原理分析如下图所示:
基于Spring MVC模块提供的HandlerInterceptor接口规范定义拦截器,对特定请求进行拦截,例如检测请求访问时间,关键代码如下,
package com.jt.web.interceptor;
public class TimeAccessInterceptor implements HandlerInterceptor {
/**
* preHandle在控制层目标方法执行之前执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
//testRequestInfo(request,handler);
LocalTime now=LocalTime.now();//JDK8中的时间对象
int hour=now.getHour();//获取当前时间对应小时
//System.out.println("hour="+hour);
log.info("hour {}",hour);
if(hour<=6||hour>=22)
throw new RuntimeException("请在6~10点进行访问");
return true;
}
}
定义配置类,实现对拦截器的注册,关键代码如下。
package com.jt.web.config;
@Configuration
public class SpringWebConfig implements WebMvcConfigurer{//web.xml
//配置spring mvc 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TimeAccessInterceptor())
.addPathPatterns("/param/*");
}
}
启动服务,对于url中path以/param/开头的进行访问测试,检测请求是否被拦截处理了。
定义Controller对象处理客户端的Notice数据请求。
本次Controller中请求处理方式采用Rest风格,REST不是一个单词,是Representational State Tramsfer 的缩写,直译过来就是表述性状态转移的缩写。概念理解起来很晦涩,其实其核心就是资源,用URL定位资源,用HTTP动作表述要做的操作,HTTP动作通常会包含GET、PUT、POST、DELETE等,每一种动作,从本质上而言就是对资源的操作,例如GET就是获取资源,用于查询资源,POST用于新增资源,PUT请求用于更新资源,DELETE请求用于删除资源。
创建SysNoticeController类,通过此类对象处理控制层分发的请求对象,关键代码如下:
package com.jt.web.controller;
@RequestMapping("/notice/")
public class SysNoticeController {// handler
//关联service,将业务逻辑交给service对象处理
@Autowired
private SysNoticeService sysNoticeService;
@GetMapping
public JsonResult doSelectNotices(SysNotice notice){
return new JsonResult(sysNoticeService.findNotices(notice));
}
@PostMapping
public JsonResult doInsertNotice(@RequestBody SysNotice notice){
sysNoticeService.saveNotice(notice);
return new JsonResult("save ok");
}
@GetMapping("{id}")
public JsonResult doSelectById(@PathVariable Long id){
return new JsonResult(sysNoticeService.findById(id));
}
@PutMapping
public JsonResult doUpdateNotice(@RequestBody SysNotice notice){
sysNoticeService.updateNotice(notice);
return new JsonResult("update ok");
}
@DeleteMapping("{ids}")
public JsonResult doDeleteById(@PathVariable Long... ids){
sysNoticeService.deleteById(ids);
return new JsonResult("delete ok");
}
}
MVC设计思想的理解
Spring Web模块中MVC设计的设计
Spring MVC中核心组件及其作用。
基于Spring MVC实现请求处理的过程。
什么是MVC(分层设计思想,一种编程套路)
MVC具体指的是哪些单词的缩写(Model,View,Controller)
举个生活中MVC的例子(全聚德饭店:菜单-view,服务员-controller,厨师-model)
Spring Web模块是如何基于MVC设计思想做具体实现的?
@RestController的作用是什么?
@ResponseBody注解的作用是什么?
@RequestMapping的作用是什么,注解内部都有什么属性?
@RestController描述的类中,其方法返回值是如何转换为json的。
404 (资源没有找到)
400 (请求参数与服务端参数不匹配)
405 (请求方式与服务端不匹配)
500 (服务端处理请求的过程中出现了问题)