spring揭秘26-springmvc06-springmvc注解驱动的web应用

文章目录

  • 【README】
  • 【1】springmvc注解驱动web应用
    • 【1.1】springmvc注解驱动web应用的3个组件
    • 【1.2】springmvc注解驱动web应用代码实践
  • 【2】springmvc常用注解
    • 【2.1】@Controller注解(标注处理器类)
    • 【2.2】@RequestMapping注解(标注处理器类或处理器方法)
      • 【2.2.1】@RequestMapping注解的params元素
      • 【2.2.2】@RequestMapping注解的consumes与produces元素
    • 【2.3】@RequestParam注解(标注方法参数)
    • 【2.4】@ModelAttribute注解(标注方法或方法参数)
      • 【2.4.1】@ModelAttribute注解标注处理器方法(HandlerMethod)
      • 【2.4.2】@ModelAttribute注解标注处理器方法参数(HandlerMethod Parameter)
    • 【2.5】@RequestBody注解标注处理器方法参数
      • 【2.5.1】@RequestBody注解标注处理器方法参数代码实践
    • 【2.6】@ResponseBody注解标注处理器方法
      • 【2.6.1】@ResponseBody注解标注处理器方法代码实践
      • 【2.6.2】@ResponseBody注解标注处理器方法调试
    • 【2.7】@ControllerAdvice注解(标注类),又叫Controller增强注解
      • 【2.7.1】@ControllerAdvice注解声明统一异常处理代码实践
    • 【2.8】@PathVariable注解(标注处理器方法参数)
      • 【2.8.1】@PathVariable注解(标注处理器方法参数)代码实践
  • 【3】处理器方法入参与出参类型(仅了解)
    • 【3.1】处理器方法常用输入参数类型
    • 【3.2】处理器方法常用返回结果参数类型

【README】

本文部分内容总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;

此外,本文还参考了springmvc官方文档: Web on Servlet Stack

1)本文代码参见: github: springmvcDiscoverAnnotationDemo

2) 本文依赖xml配置初始化springmvc容器;(如需要通过java初始化springmvc容器,也很简单,自行实现,可以参考:Enable MVC Configuration



【1】springmvc注解驱动web应用

1)springmvc提供了2种类型注解,包括 @Controller 与 @RequestMapping, 用于标注二级控制器及请求处理方法,以便HandlerMapping查找被标注的二级控制器并把请求转给该控制器做业务逻辑处理;

  • 此外,依赖注解驱动springmvc应用, 需要在springmvc容器的xml配置文件中新增 component-scan元素,用于扫描注解标注的类,实例化bean并注册到springmvc容器;

2)通过springmvc注解与component-scan可以把注解标注的处理器类实例化后注册到springmvc容器; 但有2个问题:这些处理器如何被springmvc识别用于处理web请求呢? 第二个问题,如果不继承AbstractController,springmvc框架调用处理器的哪个方法处理web请求呢(具体的,是一级控制器调用HandlerAdapter,HandlerAdapter再调用处理器方法执行业务逻辑)

由spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理) 可知,一级控制器DispatcherServlet通过web请求查找二级控制器并执行业务逻辑的步骤如下

  • 首先从HandlerMapping中查找是否存在与请求标志(如请求路径)匹配的处理器,若有则返回处理器包装对象HandlerExecutionChain;
  • 接着根据HandlerExecutionChain中封装的实际处理器查找HandlerAdapter处理器适配器;
  • 调用处理器适配器HandlerAdapter的handle方法, 处理器适配器再调用实际处理器的handle方法执行具体业务逻辑;

3)综上: 要实现通过注解驱动springmvc应用,springmvc需要提供3个组件,包括HandlerMaping, HandlerAdapter,以及Handler(注解标注的处理器,如Controller);

  • HandlerMapping: 从HandlerMapping中找出与请求标志(如URL)匹配的二级处理器(如Controller),并把二级处理器封装到HandlerExecutionChain再返回;
  • HandlerAdapter: 处理器适配器,用于上游一级控制器(如Dispatcher)调用统一的处理器接口执行业务逻辑; 适配器再调用具体二级处理器的业务逻辑方法;
  • Handler:二级处理器,封装了具体业务逻辑方法,如Controler(也就是被注解标注的目标类与方法 );


【1.1】springmvc注解驱动web应用的3个组件

1)spingmvc通过注解实现web请求处理的2个问题回顾:

  • 问题1:springmvc通过什么条件找出二级处理器处理web请求?
  • 问题2:springmvc找到二级处理器后,调用该处理器的哪个方法处理web请求?

2)springmvc提供的解决方案:

  • RequestMappingHandlerMapping: 封装请求标志(如路径)到@Controller与@RequestMapping标注的处理器的映射(具体说是处理器方法对象,类型为HandlerMethod; 这也再一次佐证处理器不止Controller一种,可以是其他类型,如HandlerMethod;当然了HandlerMethod封装了@Controller标注的处理器 );
    • 处理器方法对象HandlerMethod用beanType(类型为Class)封装 @Controller标注的类class, 用method(类型为反射的Method)封装@RequestMapping标注的方法;
    • 这在理论上解决了问题1与问题2; 为什么是理论上 ? 因为只是找到了具体的处理器方法(HandlerMethod),但如何调用处理器的处理方法,需要依赖于HandlerAdapter;
  • RequestMappingHandlerAdapter:DispatcherServlet传入request与处理器方法HandlerMethod对象到适配器handle方法,适配器handle方法调用HandlerMethod的handleInternal处理方法,底层通过反射调用到具体处理器的处理方法(@Controller标注类中@RequestMapping标注的方法)

在这里插入图片描述

【补充】invocableMethod封装了HandlerMethod对象;

3)springmvc注解驱动web应用的3个组件:

  • RequestMappingHandlerMapping: 封装请求标志(如路径)到@Controller与@RequestMapping标注的处理器(具体是HandlerMethod处理器方法对象)的映射
  • RequestMappingHandlerAdapter:DispatcherServlet传入request与处理器方法HandlerMethod对象到适配器handle方法,进而通过反射调用HandlerMethod的具体处理方法;
  • 这里只有2个组件,还有1个组件呢? 最后一个组件就是我们的二级处理器Handler,即被@Controller与@RequestMapping标注的处理器;


【1.2】springmvc注解驱动web应用代码实践

1)说明: 本文依赖xml配置初始化springmvc容器;(如需要通过java编码初始化springmvc容器,也很简单,自行实现,可以参考:Enable MVC Configuration

2)xml配置初始化springmvc容器的注意点:springmvc容器的xml配置文件【applicationContext.xml】,必须新增元素 <mvc:annotation-driven/> , <context:component-scan base-package=“com.tom.springmvc” />

  • mvc:annotation-driven作用: 启用mvc配置功能,并自动注册DispatcherServlet处理请求的基础设施bean ; 如下(参考 Special Bean Types):
    • HandlerMapping(RequestMappingHandlerMapping, SimpleUrlHandlerMapping )
    • HandlerAdapter (包括但不限于 RequestMappingHandlerAdapter
    • HandlerExceptionResolver
    • ViewResolver
    • LocaleResolver, LocaleContextResolver
    • ThemeResolver
    • MultipartResolver
    • FlashMapManager
  • context:component-scan作用(回顾):自动检测被注解标注的class并注册到spring容器; 注解列表如下。参见 Classpath Scanning and Managed Components
    • @Component, @Repository, @Service,@Controller, @RestController, @ControllerAdvice, @Configuration
    • 此外,context:component-scan还默认起到 <annotation-config/>标签的作用, 还可以激活这些注解: @Required,
      @Autowired, @PostConstruct, @PreDestroy, @Resource, @PersistenceContext and @PersistenceUnit (即配置了context:component-scan,则无需配置annotation-config标签)

【web.xml】

<?xml version="1.0" encoding="UTF-8"?>
<web-appxmlns = "https://jakarta.ee/xml/ns/jakartaee"xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation = "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"version = "5.0"metadata-complete = "false"
><display-name>springmvcDiscover</display-name><!-- 指定ContextLoaderListener加载web容器时使用的多个xml配置文件(默认使用/WEB-INF/applicationContext.xml) --><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml</param-value></context-param><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 注册过滤器代理 --><filter><filter-name>customFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>customFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 配置监听器ContextLoaderListener,其加载顶层WebApplicationContext web容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) --><servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件--><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/dispatcher-servlet.xml</param-value></init-param><load-on-startup>2</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping><welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list></web-app>

【applicationContext.xml】springmvc顶级web容器加载的xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/enable.html --><!--启用mvc配置, 该元素可以注册DispatcherServlet处理请求的基础设施bean,参见 https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/special-bean-types.html --><mvc:annotation-driven/><!-- https://docs.spring.io/spring-framework/reference/core/beans/classpath-scanning.html --><!-- spring可以自动检测被注解标注的class并注册到spring容器 --><!--context:component-scan 启用了 <context:annotation-config>的功能 --><!-- 此外, context:component-scan 也包含了 AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor 功能 --><context:component-scan base-package="com.tom.springmvc" /><!-- 注册SimpleMappingExceptionResolver-处理器异常解析器 --><bean name="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="defaultErrorView" value="/error/defaultErrorPage" /><property name="exceptionAttribute" value="exceptionInfo" /><property name="exceptionMappings"><props><prop key="com.tom.springmvc.exception.TomWebException">/error/tomWebErrorPage</prop><prop key="java.lang.Exception">/error/exceptionBaseErrorPage</prop></props></property></bean></beans>

【dispatcher-servlet.xml】DispatcherServlet初始化次顶级web容器的xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册自定义处理器拦截器 --><bean id="timeCostHandlerInterceptor"  class="com.tom.springmvc.handlerinterceptor.TimeCostHandlerInterceptor"/><!-- 注册HandllerMapping bean到springweb容器, BeanNameUrlHandlerMapping使用URL与Controller的bean名称进行匹配 --><bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"><property name="interceptors"><list><ref bean="timeCostHandlerInterceptor" /></list></property></bean><!-- 注册视图解析器bean到springweb容器 --><bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/><property name="prefix" value="/WEB-INF/jsp/" /><property name="suffix" value=".jsp" /><property name="order" value="1" /></bean><!-- 注册BeanNameViewResolver视图解析器到springweb容器(一级控制器的web容器WebApplicationContext) --><bean id="beanNameViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"><property name="order" value="2" /></bean></beans>

【BankCardAnnotationController】具体的处理器方法类及处理器方法

@Controller
@RequestMapping("/annotation")
public class BankCardAnnotationController {private BankCardAppService bankCardAppService;public BankCardAnnotationController(BankCardAppService bankCardAppService) {System.out.println("BankCardAnnotationController created");this.bankCardAppService = bankCardAppService;}@RequestMapping(value = "/annotationQryCardList", method = RequestMethod.GET)public ModelAndView qryCardList(HttpServletRequest request, HttpServletResponse response) throws Exception {System.out.println("qryCardList accessed");ModelAndView modelAndView = new ModelAndView("bankCardListPage");modelAndView.addObject("bankCardList", bankCardAppService.listCard());return modelAndView;}
}

【bankCardListPage.jsp】前端展示页面jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  import="java.util.List" import="java.util.ArrayList"  isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户列表</title>
</head>
<body><table border="1" cellpadding="0" cellspacing="0" bordercolor="#000000"><tr><td>编号</td><td>银行卡号</td><td>备注</td></tr><c:forEach items="${bankCardList}" var="bankCard"><tr><td>${bankCard.id}</td><td>${bankCard.cardNo}</td><td>${bankCard.remark}</td></tr></c:forEach></table>
</body>
</html>

【访问效果】

在这里插入图片描述



【2】springmvc常用注解

springmvc常用注解清单参见(包括标注类,方法及方法参数的注解): springmvc Annotated Controllers



【2.1】@Controller注解(标注处理器类)

1)Controller注解定义:Controller是元注解: 因为它被其他注解Component标注;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {@AliasFor(annotation = Component.class)String value() default "";
}

2)@Controller注解: component-scan元素可以扫描被@Controller标注的类,并注册到spring容器; RequestMappingHandlerMapping 从springmvc容器中查找被Controller标注的处理器,作为处理请求的候选处理器,以便handlerMapping在请求映射时查找;



【2.2】@RequestMapping注解(标注处理器类或处理器方法)

1)@RequestMapping详细介绍,参见 : RequestMapping API

2)定义:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Reflective({ControllerMappingReflectiveProcessor.class})
public @interface RequestMapping {String name() default "";@AliasFor("path")String[] value() default {};@AliasFor("value")String[] path() default {};RequestMethod[] method() default {};String[] params() default {};String[] headers() default {};String[] consumes() default {};String[] produces() default {};
}

3)@RequestMapping注解:显然,它可以标注类或方法;标注类, 标识该类是处理器所在类;标注方法,标识该方法是处理请求的方法;

  • 参数:默认是path路径属性值; 若@RequestMapping同时标注类或方法,则其处理的请求路径是标注类的路径拼接上标注方法的路径;如BankCardAnnotationController的qryCardList处理方法映射的请求路径是 /annotation/annotationQryCardList


【2.2.1】@RequestMapping注解的params元素

【BankCardAnnotationController2】

@RequestMapping标注方法,且params = {“k1=v1”, “k2=v2”}, 表示请求必须带参数k1与k2,否则报请求错误404;

@Controller
@RequestMapping("/annotation2")
public class BankCardAnnotationController2 {private final BankCardAppService bankCardAppService;public BankCardAnnotationController2(BankCardAppService bankCardAppService) {this.bankCardAppService = bankCardAppService;}@RequestMapping(value = "/annotationQryCardList", params = {"k1=v1", "k2=v2"}, method = {RequestMethod.GET, RequestMethod.POST})public ModelAndView qryCardList(String k1, String k2) throws Exception {System.out.println("qryCardList accessed");System.out.println("k1=" + k1 + ", k2=" + k2);ModelAndView modelAndView = new ModelAndView("bankCardListPage");modelAndView.addObject("bankCardList", bankCardAppService.listCard());return modelAndView;}
}

【访问效果】

在这里插入图片描述



【2.2.2】@RequestMapping注解的consumes与produces元素

【注意】若@RequestMapping注解同时标注了类与方法,且带了consumes与produces元素,则方法级别的注解元素值覆盖类级别;

1)consumes元素:可以配置1个或多个mime类型,表示被标注方法可以处理的请求的MIME类型,用于匹配请求的Content-Type实体头;

  • Content-Type实体头: 用于指出http实体内容的MIME类型, 客户端发送的请求报文与服务器返回的响应报文都可以包含Content-Type;
  • 简单理解:被标注方法的consumes元素值与客户端请求实体头Content-Type进行匹配(包含逻辑运算),若匹配成功,则被标注方法可以处理该请求,否则查找下一个被标注方法
  • MIME:多用途互联网邮件扩展类型,描述报文实体的数据格式,如 application/json (json格式), text/plain (普通文本), text/html (html格式);

2)produces元素 :可以配置1个或多个mime类型,表示被标注方法可以处理的请求愿意接受的MIME类型, 用于匹配请求的Accept请求头;

  • Accept请求头: 用于指出客户端能够处理的MIME类型;可以包含多个,如 Accept: text/html,image/*
  • 简单理解:即被标注方法的produces元素值与客户端请求头Accept进行匹配,若匹配成功,则被标注方法可以处理该请求,否则查找下一个被标注方法; (若客户端没有带Accept请求头,则服务器认为客户端接收任何MIME类型的响应报文)

【BankCardAnnotationController3】

@Controller
@RequestMapping("/annotation3")
public class BankCardAnnotationController3 {private final BankCardAppService bankCardAppService;public BankCardAnnotationController3(BankCardAppService bankCardAppService) {this.bankCardAppService = bankCardAppService;}@RequestMapping(value = "/annotationQryCardList", consumes = {"text/plain;charset=UTF-8"}, produces = {"text/html;charset=UTF-8"}, method = {RequestMethod.GET, RequestMethod.POST})public ModelAndView qryCardList(String k1, String k2) throws Exception {System.out.println("qryCardList accessed");System.out.println("k1=" + k1 + ", k2=" + k2);ModelAndView modelAndView = new ModelAndView("bankCardListPage");modelAndView.addObject("bankCardList", bankCardAppService.listCard());return modelAndView;}
}

在这里插入图片描述


【补充】如果通过浏览器访问,没有指定Content-Type实体头部,则报415,HTTP状态 415 - 不支持的媒体类型 ;



【2.3】@RequestParam注解(标注方法参数)

1)@RequestParam注解定义:只能标注方法参数,使得把请求参数绑定到方法参数;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {@AliasFor("name")String value() default "";@AliasFor("value")String name() default "";boolean required() default true; // 默认被标注的方法参数一定有值String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

【BankCardAnnotationControllerWithRequestParam】 使用@RequestParam标注userName入参;把请求参数k1与userName绑定;

@Controller
@RequestMapping("/bankCardAnnotationControllerWithRequestParam")
public class BankCardAnnotationControllerWithRequestParam {private final BankCardAppService bankCardAppService;public BankCardAnnotationControllerWithRequestParam(BankCardAppService bankCardAppService) {this.bankCardAppService = bankCardAppService;}@RequestMapping(value = "/annotationQryCardList", consumes = {"text/plain;charset=UTF-8"}, produces = {"text/html;charset=UTF-8"}, method = {RequestMethod.GET, RequestMethod.POST})public ModelAndView qryCardList(@RequestParam("k1") String userName) throws Exception {System.out.println("userName=" + userName);ModelAndView modelAndView = new ModelAndView("bankCardListPage");modelAndView.addObject("bankCardList", bankCardAppService.listCard());return modelAndView;}
}

【postman访问效果】

userName=tom

在这里插入图片描述



【2.4】@ModelAttribute注解(标注方法或方法参数)

1)@ModelAttribute注解定义: 标注方法或方法参数;本章节部分内容总结自 Spring MVC and the @ModelAttribute Annotation

  • 作用:把方法参数或方法返回值作为属性绑定到ModelAndView;


【2.4.1】@ModelAttribute注解标注处理器方法(HandlerMethod)

1)@ModelAttribute注解标注方法时:它接收的参数类型与 @RequestMapping相同;但是 @ModelAttribute标注的方法不能匹配请求;

2)同一个处理器中,@ModelAttribute注解标注的方法先于@RequestMapping注解标注的方法执行;因为在执行任何处理器方法前(@RequestMapping标注),模型对象已经被创建了;(即@ModelAttribute标注的方法执行时添加到model的属性,所有的处理器方法在执行时都可以使用)

【HelloWorldAnnotationControllerWithModelAttribute】@ModelAttribute标注方法

@Controller
@RequestMapping("/helloWorld")
public class HelloWorldAnnotationControllerWithModelAttribute {private final BankCardAppService bankCardAppService;public HelloWorldAnnotationControllerWithModelAttribute(BankCardAppService bankCardAppService) {this.bankCardAppService = bankCardAppService;}@ModelAttribute("tips")public String helloWorld(@RequestParam("k1") String userName) throws Exception {System.out.println("@ModelAttribute annotates helloWorld() userName=" + userName);return "hello world";}@ModelAttributepublic void helloWorld2(ModelMap modelMap) throws Exception {System.out.println("@ModelAttribute annotates helloWorld2()");modelMap.addAttribute("tips2", "hello world 2");}@RequestMapping(value = "/helloWorld3", consumes = {"text/plain;charset=UTF-8", "text/html;charset=UTF-8"}, produces = {"application/json;charset=UTF-8"}, method = {RequestMethod.GET, RequestMethod.POST})public String helloWorld3(@RequestParam("k1") String userName, ModelMap modelMap) throws Exception {System.out.println("@RequestMapping helloWorld(): userName=" + userName);System.out.println("@RequestMapping helloWorld():  tips = " + modelMap.getAttribute("tips"));System.out.println("@RequestMapping helloWorld():  tips2 = " + modelMap.getAttribute("tips2"));return "helloWorld";}
}

【运行效果】

@ModelAttribute annotates helloWorld2()
@ModelAttribute annotates helloWorld() userName=tom
@RequestMapping helloWorld3(): userName=tom
@RequestMapping helloWorld3():  tips = hello world
@RequestMapping helloWorld3():  tips2 = hello world 2 

【代码解说】

  • 1)@ModelAttribute(“tips”)标注方法helloWorld, 该方法返回值hello world作为ModelAndView的属性值,属性名=tips(ModelAttribute注解的value元素值); 所以我们在helloWorld3()方法中可以通过ModelMap获取到tips属性值;
  • 2)ModelMap是什么: ModelMap类型为LinkedHashMap,是ModelAndView中的一个属性; ModelAndView新增对象方法addObject(),实际是把attributeName与attributeValue新增到ModelMap中; (简单理解:ModelAndView把ModelMap作为属性进行封装,操作ModelAndView就是操作ModelMap,两者可以等价 );
  • 3)@ModelAttribute(“tips”)标注方法helloWorld2(),其方法参数可以引用ModelMap,方法体内部显式操作ModelMap;

【ModelAndView】

public class ModelMap extends LinkedHashMap<String, Object> {//...
}public class ModelAndView {/** View instance or view name String. */@Nullableprivate Object view;/** Model Map. */@Nullableprivate ModelMap model; //  ModelMap model 作为 ModelAndView的一个属性  /** Optional HTTP status for the response. */@Nullableprivate HttpStatusCode status;/** Indicates whether this instance has been cleared with a call to {@link #clear()}. */private boolean cleared = false;// ModelAndView新增对象public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) {getModelMap().addAttribute(attributeName, attributeValue);return this;}// ... 
}


【2.4.2】@ModelAttribute注解标注处理器方法参数(HandlerMethod Parameter)

1)@ModelAttribute注解标注处理器方法参数: 它表明springmvc会把注解的value元素值作为属性键从ModelAndView中查找属性值,并作为与属性键同名的方法参数值调用处理器方法

  • 若ModelAndView中不存在该属性,则初始化一个属性键-属性值对,并添加到ModelAndView;

【HelloWorldAnnotationControllerWithModelAttribute】

@RequestMapping(value = "/helloWorld4", consumes = {"text/plain;charset=UTF-8", "text/html;charset=UTF-8"}, produces = {"application/json;charset=UTF-8"}, method = {RequestMethod.GET, RequestMethod.POST})public String helloWorld4(@ModelAttribute("bankCardDto") BankCardDto bankCardDto, ModelMap modelMap) throws Exception {System.out.println("@RequestMapping helloWorld4()");modelMap.addAttribute("bankCardDto", bankCardDto);bankCardDto.setId(1L);bankCardDto.setRemark(String.valueOf(modelMap.getAttribute("tips")));return "helloWorld";}

【helloWorld.jsp】

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  import="java.util.List" import="java.util.ArrayList"  isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>helloWorld</title>
</head>
<body>tips:  ${tips} <br>tips2:  ${tips2} <br>bankCardDto:  id = ${bankCardDto.id};cardNo = ${bankCardDto.cardNo}; remark = ${bankCardDto.remark};
</body>
</html>

【执行效果】

@ModelAttribute annotates helloWorld() userName=tom
@ModelAttribute annotates helloWorld2()
BankCardDto Constructor without arguments // 显然ModelAndView没有属性=bankCardDto的值,所以ModelAndView初始化一个(调用构造器方法)
@RequestMapping helloWorld4()

在这里插入图片描述



【2.5】@RequestBody注解标注处理器方法参数

1)@RequestBody注解标注处理器方法参数: 表示springmvc读取请求体并通过HttpMessageConverter反序列化为Object对象,作为处理器方法参数调用方法;

2) 使用@RequestBody或@ResponseBody,需要注册MappingJackson2HttpMessageConverter 消息转换器; 只需要引入jackson-core与jackson-databind制品库即可;springmvc在classpath下检测到jackson制品库存在,则springmvc启动时默认注册MappingJackson2HttpMessageConverter (无需我们手动注册);

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {/*** Whether body content is required.* <p>Default is {@code true}, leading to an exception thrown in case* there is no body content. Switch this to {@code false} if you prefer* {@code null} to be passed when the body content is {@code null}.* @since 3.2*/boolean required() default true;}

@RequestBody只能标注参数

3)@RequestBody的作用是把请求体反序列化为Object对象; 请求体通常采用json格式,所以处理器方法@RequestMapping的consumes应该设置为application/json;


【pom.xml】引入jackson-core与jackson-databind依赖(制品库)

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.18.0</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.18.0</version>
</dependency>


【2.5.1】@RequestBody注解标注处理器方法参数代码实践

【HelloWorldControllerRequestBody】

@Controller
@RequestMapping("/helloWorldControllerRequestBody")
public class HelloWorldControllerRequestBody {public HelloWorldControllerRequestBody() {// do nothing.}@RequestMapping(value = "/helloWorldRequestBody", consumes = {"application/json;charset=UTF-8"}, produces = {"text/html;charset=UTF-8", "application/json;charset=UTF-8"}, method = {RequestMethod.POST})public String helloWorldRequestBody(@RequestBody BankCardDto bankCardDto, ModelMap modelMap) throws Exception {System.out.println("@RequestMapping @RequestBody helloWorldRequestBody()");modelMap.addAttribute("bankCardDto", bankCardDto);bankCardDto.setRemark(bankCardDto.getCardNo() + "-@RequestBody备注");return "helloWorld";}
}

【访问效果】

在这里插入图片描述


3)默认情况下,以下HttpMessageConverters实例被springmvc自动注册,参见 springmvc Http Message Converters

  • ByteArrayHttpMessageConverter – converts byte arrays (自动注册,无需条件
  • StringHttpMessageConverter – converts Strings (自动注册,无需条件
  • ResourceHttpMessageConverter – converts org.springframework.core.io.Resource for any type of octet stream (自动注册,无需条件
  • SourceHttpMessageConverter – converts javax.xml.transform.Source (自动注册,无需条件
  • FormHttpMessageConverter – converts form data to/from a MultiValueMap<String, String> (自动注册,无需条件
  • Jaxb2RootElementHttpMessageConverter – converts Java objects to/from XML (added only if JAXB2 is present on the classpath)
  • MappingJackson2HttpMessageConverter – converts JSON (added only if Jackson 2 is present on the classpath) 只要Jackson2类在classpath路径下存在
  • MappingJacksonHttpMessageConverter – converts JSON (added only if Jackson is present on the classpath) 只要Jackson类在classpath路径下存在
  • AtomFeedHttpMessageConverter – converts Atom feeds (added only if Rome is present on the classpath)
  • RssChannelHttpMessageConverter – converts RSS feeds (added only if Rome is present on the classpath)


【2.6】@ResponseBody注解标注处理器方法

部分内容总结自: springmvc @ResponseBody

1)@ResponseBody注解标注处理器方法: 表示springmvc通过HttpMessageConverter把返回结果序列化为响应报文体;

2) 与@RequestBody类似,使用@ResponseBody,需要注册MappingJackson2HttpMessageConverter 消息转换器; 只需要引入jackson-core与jackson-databind制品库即可; 注册方法参见2.5章节

【@ResponseBody】@ResponseBody可以标注类与方法(具体的,标注处理器类与处理器方法)

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {}

3)@ResponseBody也支持标注处理器类, 所有的处理器方法都继承处理器类的标注,即所有处理器方法的返回结果都会被HttpMessageConverter序列化为响应报文体;

  • 这实际上是@RestController 起的作用,@RestController是包含@Controller与@ResponseBody的元注解

【@RestController】

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller // 控制器类注解
@ResponseBody  // 返回结果序列化为响应报文体注解 
public @interface RestController {/*** The value may indicate a suggestion for a logical component name,* to be turned into a Spring bean in case of an autodetected component.* @return the suggested component name, if any (or empty String otherwise)* @since 4.0.1*/@AliasFor(annotation = Controller.class)String value() default "";}


【2.6.1】@ResponseBody注解标注处理器方法代码实践

【HelloWorldControllerResponseBody】

@Controller
@RequestMapping("/helloWorldControllerResponseBody")
public class HelloWorldControllerResponseBody {private final BankCardAppService bankCardAppService;public HelloWorldControllerResponseBody(BankCardAppService bankCardAppService) {this.bankCardAppService = bankCardAppService;}@RequestMapping(value = "/helloWorldResponseBody", consumes = {"application/json;charset=UTF-8"}, produces = {"application/json;charset=UTF-8"}, method = {RequestMethod.POST})@ResponseBodypublic List<BankCardDto> helloWorldResponseBody() throws Exception {System.out.println("@RequestMapping @ResponseBody helloWorldResponseBody()");return bankCardAppService.listCard();}
}

【访问效果】 处理器方法helloWorldResponseBody返回结果是List<BankCardDto>, 因为处理器方法被@ResponseBody标注,所以springmvc会通过HttpMessageConverter(具体是MappingJackson2HttpMessageConverter 消息转换器)序列化为json格式的响应报文体;

在这里插入图片描述



【2.6.2】@ResponseBody注解标注处理器方法调试

在这里插入图片描述



【2.7】@ControllerAdvice注解(标注类),又叫Controller增强注解

1)@ControllerAdvice注解定义的背景:

  • 问题:@ExceptionHandler、@InitBinder 和 @ModelAttribute 方法仅适用于被 @Controller 标注的类或类层次结构,无法应用于springmvc应用中所有被 @Controller标注的类; 即每个处理器(Controller)都需要写一套@ExceptionHandler、@InitBinder 和 @ModelAttribute , 导致代码冗余
  • 解决方法:引入@ControllerAdvice与@RestControllerAdvice注解; 在@ControllerAdvice或@RestControllerAdvice注解标注的类中使用@ExceptionHandler、@InitBinder 和 @ModelAttribute 标注的方法可以应用于任何控制器;最大的优点之一是可以把整个springmvc应用的异常处理逻辑集中在一个类来处理,而不是散落在各个处理器类中


【2.7.1】@ControllerAdvice注解声明统一异常处理代码实践

总结自: Understanding Spring’s @ControllerAdvice

1)@ControllerAdvice注解声明统一异常处理的关注点:

  • 创建自身的异常类;
  • 一个应用只有一个@ControllerAdvice标注的类;
  • 在ControllerAdvice类中编写处理异常方法,并使用@ExceptionHandler标注;
  • 对于每一个异常都提供给一个异常处理器;

【GlobalExceptionHandler】全局异常处理器

@ControllerAdvice
public class GlobalExceptionHandler {private static final Log LOGGER = LogFactory.getLog(GlobalExceptionHandler.class);public GlobalExceptionHandler() {// do nothing.}@ExceptionHandler({TomWebException.class, Exception.class})public ResponseEntity<TomWebModel> handleException(Exception ex, WebRequest request) {// 打印异常栈LOGGER.error("GlobalExceptionHandler#handleException() method handle exception. ", ex);HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);if (ex instanceof TomWebException tomWebException) {return new ResponseEntity<>(new TomWebModel(tomWebException), headers, HttpStatus.OK);}return new ResponseEntity<>(new TomWebModel("000000", ex.getMessage()), headers, HttpStatus.OK);}
}

【TomWebException】异常类

public class TomWebException extends RuntimeException {private String code;private String message;public TomWebException() {super();}public TomWebException(String message) {super("TomWebException-" + message);}public TomWebException(String code, String message) {super(code + "-" + message);this.code = code;this.message = message;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}@Overridepublic String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}

【TomWebModel】异常类对应的异常信息模型

public class TomWebModel {private String code;private String message;public TomWebModel() {// do nothing}public TomWebModel(String code, String message) {this.code = code;this.message = message;}public TomWebModel(TomWebException tomWebException) {this.code = tomWebException.getCode();this.message = tomWebException.getMessage();}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}

【TomWebThrowExceptionController】抛出异常的处理器

@Controller
public class TomWebThrowExceptionController {@RequestMapping("/throwException")protected ModelAndView throwException(HttpServletRequest request, HttpServletResponse response) throws Exception {if (Objects.isNull(request.getParameter("testParamKey"))) {throw new TomWebException(BusiDateUtils.getNowTextYearToSecond() + " testParamKey查无记录");}return new ModelAndView("index");}@RequestMapping("/throwExceptionWithCode")protected ModelAndView throwExceptionWithCode(HttpServletRequest request, HttpServletResponse response) throws Exception {if (Objects.isNull(request.getParameter("testParamKey"))) {throw new TomWebException("TOM001", BusiDateUtils.getNowTextYearToSecond() + " testParamKey查无记录");}return new ModelAndView("index");}
}

【异常捕获效果】

在这里插入图片描述



【2.8】@PathVariable注解(标注处理器方法参数)

总结自 Spring @PathVariable Annotation

1) @PathVariable注解(标注处理器方法参数): @PathVariable注解被用于解析请求路径节点,并把路径节点绑定到方法参数;



【2.8.1】@PathVariable注解(标注处理器方法参数)代码实践

【HelloWorldControllerPathVariable】

@Controller
@RequestMapping("/sichuan")
public class HelloWorldControllerPathVariable {private final BankCardAppService bankCardAppService;public HelloWorldControllerPathVariable(BankCardAppService bankCardAppService) {this.bankCardAppService = bankCardAppService;}@RequestMapping(value = "/{city}/{user}", consumes = {"application/json;charset=UTF-8"}, produces = {"application/json;charset=UTF-8"}, method = {RequestMethod.POST})@ResponseBodypublic UserDto findUser(@PathVariable("city") String city, @PathVariable("user") String userName) throws Exception {return UserDto.build(System.currentTimeMillis(), userName, city);}
}

【访问效果】

在这里插入图片描述



【3】处理器方法入参与出参类型(仅了解)

【3.1】处理器方法常用输入参数类型

参见: springmvc handler method arguments

1)处理器方法常用输入参数类型:

  • WebRequest
    jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse, jakarta.servlet.http.HttpSession

  • HttpMethod

  • java.util.Locale

  • java.util.TimeZone + java.time.ZoneId

  • java.io.InputStream, java.io.Reader

  • java.io.OutputStream, java.io.Writer

  • @PathVariable

  • @RequestParam

  • @RequestHeader

  • @CookieValue

  • @RequestBody

  • HttpEntity

  • @RequestPart

  • java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap

  • RedirectAttributes

  • @ModelAttribute

  • Errors, BindingResult

  • SessionStatus + class-level @SessionAttributes

  • UriComponentsBuilder

  • @SessionAttribute

  • @RequestAttribute



【3.2】处理器方法常用返回结果参数类型

参见: springmvc return typs

1)处理器方法常用返回结果参数类型:

  • @ResponseBody

  • HttpEntity, ResponseEntity

  • HttpHeaders

  • ErrorResponse

  • String

  • View

  • java.util.Map, org.springframework.ui.Model

  • @ModelAttribute

  • ModelAndView object

  • void

  • Callable

  • ListenableFuture, java.util.concurrent.CompletionStage, java.util.concurrent.CompletableFuture
    StreamingResponseBody



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1556214.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

OpenAI董事会主席Bret Taylor的Agent公司Sierra:专注于赋能下一代企业用户体验

本文由readlecture.cn转录总结。ReadLecture专注于音、视频转录与总结&#xff0c;2小时视频&#xff0c;5分钟阅读&#xff0c;加速内容学习与传播。 视频来源 youtube: https://www.youtube.com/watch?vriWB5nPNZEM&t47s 大纲 介绍 欢迎与介绍 介绍Bret Taylor&#x…

如 有 任 何 问 题 ,请 及 时 联 系 我 们 反 馈 !

如有任何问题&#xff0c; 请及时联系我们反馈 !https://support.qq.com/products/671606 如有任何问题&#xff0c; 请及时联系我们反馈 !

基金好书入门阅读笔记《基金作战笔记:从投基新手到配置高手的进阶之路》2

买基金&#xff0c;说到底是买基金所持有的一揽子资产。那么&#xff0c;常见的可投资产都有哪些类型呢&#xff1f; 图2.9进行了系统性的梳理&#xff0c;我们把资产分为四大类&#xff0c;分别是权益类、固收类、现金和另 类&#xff0c;下面就一一解读。 年化收益率是把一段…

Bluetooth Channel Sounding中关于CS Procedure的详细介绍

目录 BLE CS 过程定义&#xff1a; BLE CS 过程的组成部分 开始一个BLE CS 过程 与BLE CS过程相关的参数设置 BLE CS 过程定义&#xff1a; BLE 的CS特性包含一组LL层和空口协议的组合过程&#xff0c;该过程可以使得两个BLE 设备以紧密互锁的方式&#xff0c;在多个信道上…

人工智能专业就业方向与前景

随着产业结构升级的持续推进&#xff0c;未来行业领域对于人工智能专业人才的需求量会逐渐增加&#xff0c;一部分高校也开始陆续在本科阶段开设人工智能专业&#xff0c;以缓解人工智能领域人才缺口较大的问题。下面是小编整理的人工智能专业就业方向与前景&#xff0c;欢迎阅…

第五节——转移表(让你不再害怕指针)

文章目录 制作简易计算器什么是转移表&#xff1f;switch函数实现函数指针数组实现 制作简易计算器 要求&#xff1a;制作一个简易计算器&#xff0c;可以进行* / - 等功能运算。 什么是转移表&#xff1f; 指的就是通过函数指针数组的方式通过数组去调用里面的函数&#x…

儿童需要学习C++多久才能参加信息学奥赛的CSP-J比赛?

信息学奥赛&#xff08;NOI&#xff09;是国内编程竞赛领域的顶尖赛事&#xff0c;而对于初学者来说&#xff0c;参加NOI的第一步通常是通过CSP-J&#xff08;全国青少年信息学奥林匹克联赛初赛&#xff09;&#xff0c;这也是面向青少年程序员的入门级竞赛。作为信息学奥赛的基…

DDR6 或将出炉 (含 LPDDR6, CAMM)

前记:目前DDR6 尚未问世,文中较多信息是“据说”,笔者也无法考证,请大家后期以JEDEC官方为准。 很多朋友可能还没用上DDR5,但不好意思的是,DDR6 可能马上就要出现了。 三星和海力士较早开始DDR6 的设计,预计2025年商业化。 DDR6 速度 来源: 半导体观察 DDR6的速度…

AcWing 662:点的坐标 ← 结构体 or 三目运算符

【题目来源】https://www.acwing.com/problem/content/664/【题目描述】 给定两个保留一位小数的浮点数 X,Y&#xff0c;用来表示一个点的横纵坐标。 请你判断该点在坐标系中的位置。 【输入格式】 共一行&#xff0c;包含两个浮点数 X,Y&#xff0c;表示点的横纵坐标。【输出格…

毕业设计 深度学习社交距离检测系统(源码+论文)

文章目录 0 前言1 项目运行效果2 设计原理3 相关技术3.1 YOLOV43.2 基于 DeepSort 算法的行人跟踪 4 最后 0 前言 &#x1f525;这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;…

卷积层是如何学习到图像特征的?

你好啊&#xff0c;我是董董灿。 想搞懂这个问题&#xff0c;需要先了解我们所说的特征指的是什么&#xff1f;然后再了解卷积核是如何学到的特征。 我们一步步来。 1、我们先来理解图像的特征 对于一张原始图像而言&#xff0c;说原始图像是相对于经过卷积处理而言的。 对…

VL53L4CD液位监测(2)----液位检测

VL53L4CD液位监测.2--液位检测 概述视频教学样品申请完整代码下载硬件准备STSW-IMG039容器特性包含必要的头文件变量定义测距函数 Ranging()液位误差补偿函数 Liquidlevelmeasureerrorcomponsate()数据轮询函数 get_data_by_polling()演示 概述 液位检测在工业自动化、环境监测…

十大时间序列预测模型

目录 1. 自回归模型 原理 核心公式 推导过程: 完整案例 2. 移动平均模型 原理 核心公式 推导过程: 完整案例 3. 自回归移动平均模型 原理 核心公式 推导过程: 完整案例 4. 自回归积分移动平均模型 原理 核心公式 推导过程 完整案例 5. 季节性自回归积分…

LeetCode讲解篇之695. 岛屿的最大面积

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们遍历二维矩阵&#xff0c;如果当前格子的元素为1进行深度优先搜索&#xff0c;将搜索过的格子置为0&#xff0c;防止重复访问&#xff0c;然后对继续深度优先搜索上下左右中为1的格子 题解代码 func maxAr…

【通信协议】一文学会异步、同步、串行、并行、单工、半双工、全双工(一)

通信方式详解&#xff1a;异步、同步、串行、并行、单工、半双工、全双工 引言一、通信方式分类概述二、串行通信与并行通信串行通信 (Serial Communication)并行通信 (Parallel Communication)串行与并行通信对比表 三、全双工、半双工、单工通信单工通信 (Simplex Communicat…

LLM+知识图谱新工具! iText2KG:使用大型语言模型构建增量知识图谱

iText2KG是一个基于大型语言模型的增量知识图谱构建工具&#xff0c;通过从文本文档中提取实体和关系来逐步构建知识图谱。该工具具有零样本学习能力&#xff0c;能够在无需特定训练的情况下&#xff0c;在多个领域中进行知识提取。它包括文档提炼、实体提取和关系提取模块&…

最新版IntelliJ IDEA 2024.2.3 创建SpringBoot项目(包含各种依赖的选择和功能)

创建SpringBoot项目 1 . 打开IDEA 选择新建项目 2. 基础项目创建 在顶端几个选项可以选择创建基本的java项目 填写项目名称,文件位置,选择构建工具 3. 下方选择springboot 选择构建的方式 三种方式虽然不同但是,基本功能都一致, Gradle-Groovy 是指使用 Groovy 语言编写…

Redis安装RedisBloom插件

Redis安装RedisBloom插件 1. 下载RedisBloom2. 安装RedisBloom3. Redis 安装RedisBloom4. 验证是否安装成功5. 其他安装方法5.1 使用 Docker 安装 RedisBloom5.2 通过 RedisStack 安装 RedisBloom 是一个 Redis 模块&#xff0c;它提供了一种高效的方式来存储和检索大数据集中的…

(笔记)第三期书生·浦语大模型实战营(十一卷王场)–书生基础岛第5关---XTuner 微调个人小助手认知

学员闯关手册&#xff1a;https://aicarrier.feishu.cn/wiki/ZcgkwqteZi9s4ZkYr0Gcayg1n1g?open_in_browsertrue 课程视频&#xff1a;https://www.bilibili.com/video/BV1tz421B72y/ 课程文档&#xff1a; https://github.com/InternLM/Tutorial/tree/camp3/docs/L1/XTuner 关…

2024.9月29日~10月6日 SSM框架项目-《电信资费管理系统》

一、数据库介绍&#xff1a; 1、account&#xff1a;帐务信息表 2、admin_info&#xff1a;管理员信息表 3、admin_role&#xff1a;管理员角色信息表 4、cost&#xff1a;资费信息表 5、privilege_info&#xff1a;权限信息表 6、role_info&#xff1a;角色信息表 7、role_pri…