1.Maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
2.创建Controller
@Controller
/**
* @Controller:创建控制器对象,控制器能够浏览器接收请求,并响应结果给浏览器,控制器也叫处理器
*/
public class QuickController {
@RequestMapping("/exam/quick")
public String quick(Model model) {
model.addAttribute("title", "Web开发");
model.addAttribute("time", LocalDateTime.now());
return "quick";
}
}
3.创建视图
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div style="margin-left: 200px">
<h3>视图技术h3>
<div th:text="${title}">div>
<div th:text="${time}">div>
div>
body>
html>
4.代码编写完成,启动运行类,在浏览器访问exam/quick地址
1.创建Controller
@Data
public class User {
private String username;
private Integer age;
}
@Controller
public class JSONViewController {
//显示JSON视图,包含json数据
@RequestMapping("/exam/json")
public void responseJson(HttpServletResponse response) throws IOException {
String json = "{\"name\":\"lisi\",\"age\":20}";
//响应
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.println(json);
out.flush();
out.close();
}
//SpringMVC支持控制器方法返回对象,由框架将对象使用jackson转为json并输出
//User--Jackson工具库的ObjectMapper对象,---user转为json格式字符串--HttpServletResponse输出
/**
* 接收请求的注解
*
* @GetMapping:接收get请求,简化的@RequestMapping(method=RequestMethod.GET)
* @PostMapping:接收post请求
* @PutMapping:接收put请求
* @DeleteMapping:接收delete请求
*/
@ResponseBody
//@RequestMapping("/exam/json2")
@GetMapping("/exam/json2")
public User getUserJson() {
User user = new User();
user.setUsername("张三");
user.setAge(22);
return user;
}
}
2.浏览器测试两个地址
1.将生成的favicon.ico拷贝到项目的resources/或resources/static/目录。
2.在你的视图文件,加入对favicon.ico的引用
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<link rel="icon" href="../favicon.ico" type="image/x-icon"/>
head>
<body>
<div style="margin-left: 200px">
<h3>视图技术h3>
<div th:text="${title}">div>
<div th:text="${time}">div>
div>
body>
html>
示例
@GetMapping("/file/t?st.html")
//?匹配只有一个字符
//GET http://localhost:8080/file/test.html(对)
//GET http://localhost:8080/file/teest.html(错)
@GetMapping("/file/t?st.html")
public String path1(HttpServletRequest request){
return "path 请求="+request.getRequestURI();
}
@GetMapping ("/images/*.gif")
*:匹配一个路径段中的 0 或多个字符
(对) GET http://localhost:8080/images/user.gif
(对) GET http://localhost:8080/images/cat.gif
(对) GET http://localhost:8080/images/.gif
(错) GET http://localhost:8080/images/gif/header.gif
(错) GET http://localhost:8080/images/dog.jpg
@GetMapping ("/images/*.gif")
public String path2(HttpServletRequest request){
return "* 请求="+request.getRequestURI();
}
@GetMapping ("/pic/**")
** 匹配 0 或多段路径, 匹配/pic 开始的所有请求
(对) GET http://localhost:8080/pic/p1.gif
(对) GET http://localhost:8080/pic/20222/p1.gif
(对) GET http://localhost:8080/pic/user
(对) GET http://localhost:8080/pic/
@GetMapping ("/pic/**")
public String path3(HttpServletRequest request){
return "/pic/**请求="+request.getRequestURI();
}
RESTFul
@GetMapping("/order/{*id}")
{*id} 匹配 /order 开始的所有请求, id 表示 order 后面直到路径末尾的所有内容。
id 自定义路径变量名称。与@PathVariable 一样使用
(对) GET http://localhost:8080/order/1001
(对) GET http://localhost:8080/order/1001/2023-02-16
@GetMapping("/order/{*id}")
public String path4(@PathVariable("id") String orderId, HttpServletRequest request){
return "/order/{*id}请求="+request.getRequestURI() + ",id="+orderId;
}
注意:@GetMapping("/order/{*id}/{*date}")无效的, {*..}后面不能在有匹配规则了
@GetMapping("/pages/{fname:\\w+}.log")
:\\w+ 正则匹配, xxx.log
(对) GET http://localhost:8080/pages/req.log
(对) GET http://localhost:8080/pages/111.log
(错) GET http://localhost:8080/pages/req.txt
(错) GET http://localhost:8080/pages/###.log
(错) GET http://localhost:8080/pages/.log
@GetMapping("/pages/{fname:\\w+}.log")
public String path5(@PathVariable("fname") String filename, HttpServletRequest request){
return "/pages/{fname:\\w}.log 请求="+request.getRequestURI() + ",filename="+filename;
}
接收参数方式
@RestController
public class ParameterController {
//一一对应,适合接收简单类型数据String、int、long、double、float,适合接收参数数量少
//http://localhost:8080/param/p1?name=lisi&age=20&sex=m
@GetMapping("/param/p1")
public String p1(String name, Integer age, String sex) {
return "接收参数:" + name + "," + age + "," + sex;
}
}
@Data
public class Person {
private String name;
private Integer age;
private String sex;
}
//使用对象接收参数,要求对象的属性名称和请求中参数名不一样,属性有set方法,类有无参数构造方法
//http://localhost:8080/param/p2?name=张三&age=22&type=asd
@GetMapping("/param/p2")
public String p2(Person person, String type) {
return "接收参数,使用对象:" + person.toString() + ",type=" + type;
}
//http://localhost:8080/param/p3?name=张三&age=22&sex=男
@GetMapping("/param/p3")
public String p3(HttpServletRequest request) {
String name = request.getParameter("name");
String age = request.getParameter("age");
String sex = request.getParameter("sex");
return "name=" + name + ",age=" + age + ",sex=" + sex;
}
//http://localhost:8080/param/p4?name=张三&age=22
@GetMapping("/param/p4")
public String p4(@RequestParam(value = "name", required = true) String name,
@RequestParam(value = "age", required = false, defaultValue = "26") Integer age) {
return "p4,name=" + name + ",age=" + age;
}
//http://localhost:8080/param/p5?name=张三&age=22
//Accept: application/json
@GetMapping("/param/p5")
public String p5(@RequestParam(value = "name", required = true) String name,
@RequestParam(value = "age", required = false, defaultValue = "26") Integer age,
@RequestHeader("Accept") String accept) {
return "p5,name=" + name + ",age=" + age + ",Accept:" + accept;
}
@Data
public class User {
private String username;
private Integer age;
}
@PostMapping("/param/json")
public String p6(@RequestBody User user) {
return "p6,json:" + user.toString();
}
IDEA Http Client测试:
POST http://localhost:8080/param/json
Content-Type: application/json
{
"username": "lisi",
"age": 22
}
@PostMapping("/param/json2")
public String p6(Reader reader) {
StringBuffer content = new StringBuffer("");
try (BufferedReader bin = new BufferedReader(reader)) {
var line = "";
while ((line = bin.readLine()) != null) {
content.append(line);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return "p7,reader=" + content.toString();
}
IDEA Http Client 测试:
POST http://localhost:8080/param/json2
hello world !!!
@GetMapping("/param/vals")
public String getMultiVal(Integer [] id){
List<Integer> idList = Arrays.stream(id).toList();
return idList.toString();
}
测试请求:
GET http://localhost:8080/param/vals?id=11&id=12&id=13
GET http://localhost:8080/param/vals?id=11,12,13,14,15
都是成功的方式
1.添加Bean Validator Starter
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
2.创建文章数据类,添加约束注解
@Data
public class ArticleVo {
private Integer id;
@NotNull(message = "必须有作者")
private Integer userId;
@NotBlank(message = "文章必须有标题")
//@Size 认为 null是有效值。
@Size(min = 3,max = 30,message = "标题在3个字符以上")
private String title;
@NotBlank(message = "必须有副标题")
@Size(min = 5,max = 60,message = "副标题在5个字以上")
private String summary;
@DecimalMin(value = "0",message = "阅读数量不能小于0")
private Integer readCount;
@Email(message = "邮箱不符合规则")
private String email;
}
3.Controller使用Bean
@RestController
public class ArticleController {
//发布新文章
/**
* @Validated:Spring中的注解,支持JSR 303规范,还能对group验证。可以在类、方法、参数上使用
* BindingResult:包含Bean的验证结果
*/
@PostMapping("/article/add")
public Map<String, Object> addArticle(@Validated @RequestBody ArticleVo article, BindingResult br) {
Map<String, Object> map = new HashMap<>();
if (br.hasErrors()) {
List<FieldError> fieldErrors = br.getFieldErrors();
for (int i = 0, len = fieldErrors.size(); i < len; i++) {
FieldError field = fieldErrors.get(i);
map.put(i + "-" + field.getField(), field.getDefaultMessage());
}
}
return map;
}
}
1.添加group标志
@Data
public class ArticleVo {
//组就是接口名
public static interface AddArticleGroup {};
public static interface EditArticleGroup {};
@NotNull(message = "文章id必须有值", groups = {EditArticleGroup.class})
@Min(value = 1, message = "id大于0", groups = {EditArticleGroup.class})
private Integer id;
@NotNull(message = "必须有作者", groups = {AddArticleGroup.class, EditArticleGroup.class})
private Integer userId;
@NotBlank(message = "文章必须有标题", groups = {AddArticleGroup.class, EditArticleGroup.class})
//@Size 认为 null是有效值。
@Size(min = 3, max = 30, message = "标题在3个字符以上", groups = {AddArticleGroup.class, EditArticleGroup.class})
private String title;
@NotBlank(message = "必须有副标题", groups = {AddArticleGroup.class, EditArticleGroup.class})
@Size(min = 5, max = 60, message = "副标题在5个字以上", groups = {AddArticleGroup.class, EditArticleGroup.class})
private String summary;
@DecimalMin(value = "0", message = "阅读数量不能小于0", groups = {AddArticleGroup.class, EditArticleGroup.class})
private Integer readCount;
@Email(message = "邮箱不符合规则", groups = {AddArticleGroup.class, EditArticleGroup.class})
private String email;
}
2.修改Controller,不同方法增加group标志
@RestController
public class ArticleController {
@PostMapping("/article/add")
public Map<String, Object> addArticle(@Validated(ArticleVo.AddArticleGroup.class) @RequestBody ArticleVo article, BindingResult br) {
Map<String, Object> map = new HashMap<>();
if (br.hasErrors()) {
List<FieldError> fieldErrors = br.getFieldErrors();
for (int i = 0, len = fieldErrors.size(); i < len; i++) {
FieldError field = fieldErrors.get(i);
map.put(i + "-" + field.getField(), field.getDefaultMessage());
}
}
return map;
}
@PostMapping("/article/edit")
public Map<String, Object> editArticle(@Validated(ArticleVo.EditArticleGroup.class) @RequestBody ArticleVo article, BindingResult br) {
Map<String, Object> map = new HashMap<>();
if (br.hasErrors()) {
List<FieldError> fieldErrors = br.getFieldErrors();
for (int i = 0, len = fieldErrors.size(); i < len; i++) {
FieldError field = fieldErrors.get(i);
map.put(i + "-" + field.getField(), field.getDefaultMessage());
}
}
return map;
}
}
3.测试代码
POST http://localhost:8080/article/edit
Content-Type: application/json
{
"id": 10,
"userId": 10,
"title": "云原生",
"summary": "云原生SpringClod,Linux",
"readCount": 10,
"email": "[email protected]"
}
@RequestMapping("/exam/quick")
public String quick(Model model) {
model.addAttribute("title", "Web开发");
model.addAttribute("time", LocalDateTime.now());
return "quick";
}
1.创建Controller,控制器方法返回ModelAndView
@GetMapping("/hello")
public ModelAndView hello() {
//ModelAndView表示数据和视图
ModelAndView mv = new ModelAndView();
mv.addObject("name", "李四");
mv.addObject("age", 20);
mv.setViewName("hello");
return mv;
}
2.创建视图:在resources/templates创建hello.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div th:text="${name}">div> <br/>
<div th:text="${age}">div> <br/>
body>
html>
#前缀 视图文件存放位置
spring.thymeleaf.prefix=classpath:/templates/
#后缀 视图文件扩展名
spring.thymeleaf.suffix=.html
3.测试,浏览器访问
1.编写实体类
@Data
public class User {
private String username;
private Integer age;
private RoleVO roleVO;
}
@Data
public class RoleVO {
private Integer id;
private String roleName;
private String memo;
}
2.增加控制器方法
@GetMapping("/json")
@ResponseBody
public User returnJson() {
User user = new User();
user.setUsername("张三");
user.setAge(20);
RoleVO roleVO = new RoleVO();
roleVO.setRoleName("管理员");
roleVO.setMemo("具有较高的权限");
roleVO.setId(10);
user.setRoleVO(roleVO);
return user;
}
3.浏览器访问
1.ResponseEntity做控制器方法返回值
@GetMapping("/json3")
@ResponseBody
public ResponseEntity<User> returnEntity() {
User user = new User();
user.setUsername("张三");
user.setAge(20);
RoleVO roleVO = new RoleVO();
roleVO.setRoleName("管理员");
roleVO.setMemo("具有较高的权限");
roleVO.setId(10);
user.setRoleVO(roleVO);
ResponseEntity<User> response = new ResponseEntity<>(user, HttpStatus.OK);
return response;
}
2.测试
1.创建Map返回值的方法
@GetMapping("/map")
@ResponseBody
public Map<String, Object> returnMap() {
Map<String, Object> map = new HashMap<>();
map.put("name", "lisi");
map.put("age", 20);
return map;
}
2.测试
SpringMVC框架是基于Servlet技术的。以请求为驱动,围绕Servlet设计的。SpringMVC处理用户请求与访问一个Servlet是类似的,请求发送给Servlet,执行doService方法,最后响应结果给浏览器完成一次请求处理。
DispatcherServlet是核心对象,称为中央调度器。负责接收所有对Controller的请求,调用开发者的Controller处理业务逻辑,将Controller方法的返回值经过视图处理响应给浏览器。
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
}
#配置服务器
#服务器端口号
server.port=8081
#上下文访问路径
server.servlet.context-path=/api
#request、response字符编码
server.servlet.encoding.charset=UTF-8
#强制request、response设置charset字符编码
server.servlet.encoding.force=true
#配置tomcat服务器的日志
#日志路径
server.tomcat.accesslog.directory=D:/log
#启用访问日志
server.tomcat.accesslog.enabled=true
#日志文件名前缀
server.tomcat.accesslog.prefix=access_log
#日志文件日期时间
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd
#日志文件名称后缀
server.tomcat.accesslog.suffix=.log
#post 请求内容最大值,默认2M
server.tomcat.max-http-form-post-size=2000000
#服务器最大连接数
server.tomcat.max-connections=8192
#配置DispatcherServlet
spring.mvc.servlet.path=/course
#Servlet的加载顺序,越小越先加载
spring.mvc.servlet.load-on-startup=0
#时间格式,可以接受请求参数使用
spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
//测试日期参数
@GetMapping("/param/date")
@ResponseBody
public String paramDate(LocalDateTime date) {
return "日期:" + date;
}
http://localhost:8081/api/course/param/date?date=2203-02-02 12:10:10
1.创建Servlet
@WebServlet(urlPatterns = "/helloServlet",name = "HelloServlet")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("这是一个SpringBoot中的Servlet");
out.flush();
out.close();
}
}
2.扫描Servlet注解
//扫描@WebServlet注解
@ServletComponentScan(basePackages = "com.hhb.web")
@SpringBootApplication
public class Springboot13ServletFilterApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot13ServletFilterApplication.class, args);
}
}
3.测试
1.创建Servlet,不需要注解
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("这是一个登录的Servlet");
out.flush();
out.close();
}
}
2.配置Servlet
@Configuration
public class WebAppConfig {
@Bean
public ServletRegistrationBean servletRegistrationBean() {
//创建ServletRegistrationBean登录一个或多个Servlet
ServletRegistrationBean bean = new ServletRegistrationBean();
bean.setServlet(new LoginServlet());
bean.addUrlMappings("/user/login");
bean.setLoadOnStartup(1);
return bean;
}
}
3.测试
1.创建过滤器
@WebFilter(urlPatterns = "/*")
public class LogFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String uri = ((HttpServletRequest) request).getRequestURI().toString();
System.out.println("***LogFilter过滤器执行了,uri:" + uri);
chain.doFilter(request, response);
}
}
2.扫描包
@ServletComponentScan(basePackages = "com.hhb.web")
@SpringBootApplication
public class Springboot13ServletFilterApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot13ServletFilterApplication.class, args);
}
}
3.浏览器访问测试
注册Filter
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new LogFilter());
bean.addUrlPatterns("/*");
return bean;
}
1.创建两个Filter,使用之前的AuthFilter、LogFilter
public class LogFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String uri = ((HttpServletRequest) request).getRequestURI().toString();
System.out.println("LogFilter过滤器执行了,uri:" + uri);
chain.doFilter(request, response);
}
}
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String uri = ((HttpServletRequest) request).getRequestURI().toString();
System.out.println("AuthFilter过滤器执行了,uri:" + uri);
chain.doFilter(request, response);
}
}
2.创建配置类,登记Filter
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean log = new FilterRegistrationBean();
log.setFilter(new LogFilter());
log.setOrder(1);
log.addUrlPatterns("/*");
return log;
}
@Bean
public FilterRegistrationBean authFilter() {
FilterRegistrationBean auth = new FilterRegistrationBean();
auth.setFilter(new AuthFilter());
auth.setOrder(2);
auth.addUrlPatterns("/*");
return auth;
}
3.测试Filter
1.登记CommonsRequestLoggingFilter
@Bean
public FilterRegistrationBean addOtherFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
//创建Filter对象
CommonsRequestLoggingFilter commonLog = new CommonsRequestLoggingFilter();
//包含请求uri
commonLog.setIncludeQueryString(true);
//登记Filter
filterRegistrationBean.setFilter(commonLog);
//拦截所有地址
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
2.设置日志级别为debug,修改application.properties
logging.level.web=debug
3.测试访问
创建监听器
@WebListener("Listener 的描述说明")
public class MySessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSessionListener.super.sessionCreated(se);
}
}
public interface WebMvcConfigurer {
//帮助配置 HandlerMapping
default void configurePathMatch(PathMatchConfigurer configurer) {}
//处理内容协商
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
//异步请求
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
//配置默认 servlet
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
//配置内容转换器
default void addFormatters(FormatterRegistry registry) {}
//配置拦截器
default void addInterceptors(InterceptorRegistry registry) {}
//处理静态资源
default void addResourceHandlers(ResourceHandlerRegistry registry) {}
//配置全局跨域
default void addCorsMappings(CorsRegistry registry) {}
//配置视图页面跳转
default void addViewControllers(ViewControllerRegistry registry) {}
//配置视图解析器
default void configureViewResolvers(ViewResolverRegistry registry) {}
//自定义参数解析器,处理请求参数
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
//自定义控制器方法返回值处理器
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}
//配置 HttpMessageConverters
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
//配置 HttpMessageConverters
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
//配置异常处理器
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
//扩展异常处理器
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
//JSR303 的自定义验证器
default Validator getValidator() {
return null;
}
//消息处理对象
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
SpringBoot中使用页面视图,比如Thymeleaf。要跳转显示某个页面,必须通过Controller对象。也就是我们需要创建一个Controller,转发到一个视图才行。如果我们现在需要显示页面,可以无需这个Controller。addViewControllers()完成从请求到视图跳转。
项目代码结构:
1.创建视图
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h3>欢迎!!!h3>
body>
html>
2.创建SpringMVC配置类
//SpringMVC配置:使用JavaConfig的方式配置SpringMVC,代替原来的xml配置文件
@Configuration
public class MvcSettings implements WebMvcConfigurer {
//页面跳转控制器,从请求直达视图页面(无需controller)
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//配置页面的控制:addViewController("请求uri").setViewName("目标视图")
registry.addViewController("/welcome").setViewName("index");
}
}
3.测试功能
Formatter是数据转换接口,将一种数据类型转换为另一种数据类型。与Formatter功能类似的还有Converter。本章研究Formatter接口。
Formatter只能将String类型转换为其他数据类型,这点在Web应用适用更广,因为Web请求的所有参数都是String,我们需要把String转为Integer、Long、Date等等。
Spring中内置了一些Formatter:
接口原型:
public interface Formatter<T> extends Printer<T>, Parser<T> {}
需求:将“1111;2222;333,NF;4;561”接收,代码中用DeviceInfo存储参数值
1.创建DeviceInfo数据类
@Data
public class DeviceInfo {
private String item1;
private String item2;
private String item3;
private String item4;
private String item5;
}
2.自定义Formatter
//将请求参数String转为DeviceInfo
public class DeviceFormatter implements Formatter<DeviceInfo> {
//text表示请求参数的值
@Override
public DeviceInfo parse(String text, Locale locale) throws ParseException {
DeviceInfo info = null;
if (StringUtils.hasLength(text)) {
String[] items = text.split(";");
info = new DeviceInfo();
info.setItem1(items[0]);
info.setItem2(items[1]);
info.setItem3(items[2]);
info.setItem4(items[3]);
info.setItem5(items[4]);
}
return info;
}
@Override
public String print(DeviceInfo object, Locale locale) {
StringJoiner joiner = new StringJoiner("#");
joiner.add(object.getItem1()).add(object.getItem2())
.add(object.getItem3()).add(object.getItem4())
.add(object.getItem5());
return joiner.toString();
}
}
3.登记自定义的DeviceFormatter
//转换器
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DeviceFormatter());
}
4.新建Controller接收请求设备数据
@RestController
public class DeviceController {
@PostMapping("/device/add")
public String addDeviceInfo(@RequestParam("device") DeviceInfo info){
return "接收的设备信息:"+info.toString();
}
}
5.单元测试
POST http://localhost:8080/device/add
Content-Type: application/x-www-form-urlencoded
device=1111;2222;333,NF;4;561
1.创建文章的Controller
@RestController
public class ArticleController {
@PostMapping("/article/add")
public String addArticle() {
return "发布新的文章";
}
@PostMapping("/article/edit")
public String editArticle() {
return "修改文章";
}
@DeleteMapping("/article/remove")
public String removeArticle() {
return "删除文章";
}
@GetMapping("/article/query")
public String queryArticle() {
return "查询文章";
}
}
2.创建有关的权限拦截器
public class AuthInterceptor implements HandlerInterceptor {
public static final String COMMON_USER = "zhangsan";
//判断登录的用户,能否执行相应的动作
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("AuthInterceptor权限拦截器");
//登录的用户
String loginUser = request.getParameter("loginUser");
//获取请求的uri地址
String requestURI = request.getRequestURI();
//判断用户和操作
if (COMMON_USER.equals(loginUser) && (
requestURI.startsWith("/article/add") ||
requestURI.startsWith("/article/edit") ||
requestURI.startsWith("/article/remove")
)) {
return false;
}
return true;
}
}
3.登记拦截器
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//权限拦截器
AuthInterceptor authInterceptor = new AuthInterceptor();
registry.addInterceptor(authInterceptor)
.addPathPatterns("/article/**")//拦截以article开头的所有请求
.excludePathPatterns("/article/query");//不拦截的地址
}
4.测试拦截器
###
POST http://localhost:8080/article/add
Content-Type: application/x-www-form-urlencoded
loginUser=zhangsan&title=Vue
###
GET http://localhost:8080/article/query
Content-Type: application/x-www-form-urlencoded
loginUser=zhangsan&title=Vue
1.创建登录拦截器
public class LoginInterceptor implements HandlerInterceptor {
private List<String> permitUser = new ArrayList<>();
public LoginInterceptor() {
this.permitUser = Arrays.asList("zhangsan", "lisi", "admin");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("==========LoginInterceptor执行了==========");
//获取登录的用户名称
String loginUser = request.getParameter("loginUser");
//只有三个用户能够访问系统
if (StringUtils.hasText(loginUser) && permitUser.contains(loginUser)) {
return true;
}
return false;
}
}
2.登记拦截器,设置顺序order
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//权限拦截器
AuthInterceptor authInterceptor = new AuthInterceptor();
registry.addInterceptor(authInterceptor)
.order(2)
.addPathPatterns("/article/**")//拦截以article开头的所有请求
.excludePathPatterns("/article/query");//不拦截的地址
//登录拦截器
LoginInterceptor loginInterceptor = new LoginInterceptor();
registry.addInterceptor(loginInterceptor)
.order(1)//顺序,整数值,越小越先执行
.addPathPatterns("/**")//拦截所有对controller的请求
.excludePathPatterns("/article/query");//排除
}
MultipartFile API
方法 | 作用 |
---|---|
getName() | 参数名称 |
getOriginalFilename() | 上传文件原始名称 |
isEmpty() | 上传文件是否为空 |
getSize() | 上传的文件字节大小 |
getInputStream() | 文件的InputStream,可用于读取部件的内容 |
transferTo(File dest) | 保存上传文件到目标dest |
1.服务器创建目录存放上传后的文件
2.创建index.html作为上传后的显示页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h3>文件上传成功!!!h3>
body>
html>
3.创建上传文件页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div style="margin-left: 200px">
<h3>文件上传h3>
<form action="uploadFile" enctype="multipart/form-data" method="post">
选择文件:<input type="file" name="upfile"><br>
<input type="submit" value="上传">
form>
div>
body>
html>
4.创建Controller
package com.hhb.upload.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Controller
public class UploadFileController {
//上传文件
@PostMapping("/uploadFile")
public String uploadFile(@RequestParam("upfile") MultipartFile multipartFile) throws IOException {
System.out.println("开始处理上传文件");
Map<String, Object> info = new HashMap<>();
if (!multipartFile.isEmpty()) {
info.put("上传文件的参数名称", multipartFile.getName());
info.put("内容类型", multipartFile.getContentType());
var ext = "unknown";
//原始文件名称,例如a.gif
var filename = multipartFile.getOriginalFilename();
//indexOf(".") 判断"."第一次出现的位置
if (filename.indexOf(".") > 0) {
//截取"."之后的字符
ext = filename.substring(filename.indexOf(".") + 1);
}
//生成服务器使用的文件名称
var newFileName = UUID.randomUUID().toString() + "." + ext;
//存储服务器的文件
var path = "D://upload//" + newFileName;
//把文件保存到path目录
multipartFile.transferTo(new File(path));
info.put("文件名称", newFileName);
}
System.out.println("info = " + info);
//重定向到index页面,防止刷新,避免重复上传
return "redirect:/index.html";
}
}
5.测试
浏览器访问 http://localhost:8080/upload.html
文件上传,查看 D:/upload 目录上传的文件
SpringBoot默认单个文件最大支持1MB,一次请求最大10MB,改变默认值,需要application修改配置项。
#一次请求最大支持5MB
spring.servlet.multipart.max-request-size=5MB
#单个文件最大支持100KB
spring.servlet.multipart.max-file-size=100KB
#超过指定大小,直接写文件到磁盘,不再内存处理
spring.servlet.multipart.file-size-threshold=0KB
配置错误页面**(规定格式)**
resources/static/error/5xx.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h3>文件上传错误h3>
body>
html>
原生的Servlet规范的文件上传
@Controller
public class UploadAction {
@PostMapping("/files")
public String upload(HttpServletRequest request) throws ServletException, IOException {
for (Part part : request.getParts()) {
String filename = extractFileName(part);
//将文件写入服务器的目录
part.write(filename);
}
return "redirect:/index.html";
}
private String extractFileName(Part part) {
String contentDisp = part.getHeader("content-disposition");
System.out.println("contentDisp=" + contentDisp);
String[] items = contentDisp.split(";");
for (String s : items) {
if (s.trim().startsWith("filename")) {
return s.substring(s.indexOf("=") + 2, s.length() - 1);
}
}
return "";
}
}
application文件,配置服务器存储文件位置
spring.servlet.multipart.location=D://upload
前端请求页面
DOCTYPE html>
<html lang="en">
<body>
<div style="margin-left: 200px">
<h3>文件上传h3>
<form action="file" enctype="multipart/form-data" method="post">
选择文件-1:<input type="file" name="upfile"> <br/> <br/>
选择文件-2:<input type="file" name="upfile"> <br/> <br/>
选择文件-3:<input type="file" name="upfile"> <br/> <br/>
<input type="submit" value="上传">
form>
div>
body>
html>
1.创建输入数字的页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="divide" method="post">
除 数:<input type="text" name="n1"><br>
被除数:<input type="text" name="n2"><br>
<input type="submit" value="计算">
form>
body>
html>
2.创建控制器,计算两个整数相除
@RestController
public class NumberController {
@PostMapping("/divide")
public String some(Integer n1, Integer n2) {
int res = n1 / n2;
return "n1/n2=" + res;
}
}
3.浏览器访问input.html,计算两个数字相除
4.创建自定义异常处理器
/**
* 1.在类的上面加@ControllerAdvice、@RestControllerAdvice,灵活组合
* 2.在类中自定义方法,处理各种异常。方法定义同Controller类中的方法的定义
*/
//控制器增强,给Controller增加异常处理功能,类似AOP的思想
@ControllerAdvice
public class GlobalExceptionHandler {
//定义方法处理数学异常
/**
* @ExceptionHandler:指定处理异常的方法 位置:在方法的上面
* 属性:是异常类的class数组,如果你的系统抛出的异常类型与@ExceptionHandler声明的相同,由当前方法处理异常
*/
/*@ExceptionHandler({ArithmeticException.class})
public String handlerArithmeticException(ArithmeticException e, Model model) {
String error = e.getMessage();
model.addAttribute("error", error);
return "exp";//视图
}*/
//不带视图,直接返回数据
@ExceptionHandler({ArithmeticException.class})
@ResponseBody
public Map<String, String> handlerReturnDataException(ArithmeticException e) {
Map<String, String> error = new HashMap<>();
error.put("msg", e.getMessage());
error.put("tips", "被除数不能为0");
return error;
}
}
5.创建错误提示页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h3>显示异常的信息:
<div th:text="${error}">div>
h3>
body>
html>
1.添加JSR-303依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.创建Bean对象,属性加入JSR-303注解
@Data
public class OrderVO {
@NotBlank(message = "订单名称不能为空")
private String name;
@NotNull(message = "商品必须有数量")
@Range(min = 1, max = 99, message = "一个订单的商品数量在{min}--{max}")
private Integer amount;
@NotNull(message = "用户不能为空")
@Min(value = 1, message = "从1开始")
private Integer userId;
}
3.Controller接收请求
@RestController
public class OrderController {
@PostMapping("/order/new")
public String createOrder(@Validated @RequestBody OrderVO orderVO) {
return "订单信息:" + orderVO.toString();
}
}
4.创建异常处理器
@ControllerAdvice
public class GlobalExceptionHandler {
//处理JSR303 验证参数的异常
//@ExceptionHandler({BindException.class})
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseBody
public Map<String, Object> handlerJSR303Exception(MethodArgumentNotValidException e) {
System.out.println("**********JSR303**********");
Map<String, Object> map = new HashMap<>();
BindingResult result = e.getBindingResult();
if (result.hasErrors()) {
List<FieldError> errors = result.getFieldErrors();
for (int i = 0, len = errors.size(); i < len; i++) {
FieldError field = errors.get(i);
map.put(field.getField(), field.getDefaultMessage());
}
}
return map;
}
}
5.测试
POST http://localhost:8080/order/new
Content-Type: application/json
{
"name": "",
"amount": 0,
"userId": 10
}
”Problem Details“ 的 JSON 应答格式
{
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/transactions/abc"
}
标准字段 | 描述 | 是否必须 |
---|---|---|
type | 标识错误类型的URI。在浏览器中加载这个URI应该转向这个错误的文档。默认为 about:blank | 可认为是 |
title | 问题类型的简单描述 | 否 |
datail | 错误信息详细描述 | 否 |
instance | 标识该特定故障实例的URI,它可以作为发生的这个错误的ID | 否 |
status | 错误使用的HTTP状态代码,它必须与实际状态匹配 | 否 |
…
application/problem+json
或application/problem+xml
。返回错误的HTTP响应应在其Content-Type
响应标头中包含适当的内容类型,并且客户端可以检查该标头以确认格式。1.新建图书的Record(普通的POJO类都是可以的)
public record BookRecord(String isbn,String name,String author) {
}
2.创建存储多本图书的容器类
@Setter
@Getter
@ConfigurationProperties(prefix = "product")
//将数据存入到BookContainer
public class BookContainer {
private List<BookRecord> books;
}
3.application.yml配置图书基础数据
#指定初始的book数据,代替数据库
product:
books:
- isbn: B001
name: java
author: lisi
- isbn: B002
name: tomcat
author: zhangsan
- isbn: B003
name: jvm
author: wangwu
#访问路径
server:
servlet:
context-path: /api
4.新建自定义异常类
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException() {
super();
}
public BookNotFoundException(String message) {
super(message);
}
}
5.新建控制器类
@RestController
public class BookController {
//注入BookContainer
@Autowired
private BookContainer bookContainer;
//根据isbn查询图书,如果没有查到,抛出异常
@GetMapping("/book")
public BookRecord getBook(String isbn) {
Optional<BookRecord> bookRecord = bookContainer.getBooks().stream().filter(el ->
el.isbn().equals(isbn)
).findFirst();
if (bookRecord.isEmpty()) {
throw new BookNotFoundException(" isbn :" + isbn + "->没有此图书");
}
return bookRecord.get();
}
}
6.新建异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
//定义的ProblemDetail
@ExceptionHandler({BookNotFoundException.class})
public ProblemDetail handlerBookNotFoundException(BookNotFoundException e) {
//ProblemDetail
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
//type:异常类型,是一个uri,uri找到解决问题的途径
problemDetail.setType(URI.create("/api/probs/not-found"));
problemDetail.setTitle("图书异常");
//增加自定义的字段
problemDetail.setProperty("时间", Instant.now());
problemDetail.setProperty("客服", "[email protected]");
return problemDetail;
}
}
7.测试接口
修改异常处理器GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
//返回ErrorResponse
@ExceptionHandler({BookNotFoundException.class})
public ErrorResponse handlerException(BookNotFoundException e) {
ErrorResponseException error = new ErrorResponseException(HttpStatus.NOT_FOUND, e);
return error;
}
}
测试接口
1.创建新的异常类继承ErrorResponseException
//自定义异常类,让SpringMVC的异常处理器使用
public class IsbnNotFoundException extends ErrorResponseException {
public IsbnNotFoundException(HttpStatus httpStatus, String detail) {
super(httpStatus, createProblemDetail(httpStatus, detail), null);
}
private static ProblemDetail createProblemDetail(HttpStatus status, String detail) {
//封装RFC7807字段
ProblemDetail problemDetail = ProblemDetail.forStatus(status);
problemDetail.setType(URI.create("/api/probs/not-found"));
problemDetail.setTitle("图书异常");
problemDetail.setDetail(detail);
//自定义字段
problemDetail.setProperty("严重程度", "低");
problemDetail.setProperty("客服邮箱", "[email protected]");
return problemDetail;
}
}
2.抛出IsbnNotFoundException
@RestController
public class BookController {
//注入BookContainer
@Autowired
private BookContainer bookContainer;
//根据isbn查询图书,如果没有查到,抛出异常
@GetMapping("/book")
public BookRecord getBook(String isbn) {
Optional<BookRecord> bookRecord = bookContainer.getBooks().stream().filter(el ->
el.isbn().equals(isbn)
).findFirst();
if (bookRecord.isEmpty()) {
//throw new BookNotFoundException(" isbn :" + isbn + "->没有此图书");
throw new IsbnNotFoundException(HttpStatus.NOT_FOUND, " isbn :" + isbn + "->没有此图书");
}
return bookRecord.get();
}
}
3.启动RFC7807支持,修改application.yml,增加配置
spring:
mvc:
problemdetails:
enabled: true