SpringMVC(二)

SpringMVC(二)

一、视图解析

hello world

1、修改返回页面的路径
1)forward:转发

forward:前缀的转发,不会由配置的视图解析器拼串
forward:转发的路径

  • 没有forward:转发之前用相对路径
    在这里插入图片描述
  • 用forward:转发
/**
	 *  forward:转发到一个页面
	 *  /hello.jsp:转发当前项目下的hello;
	 *  
	 *  一定加上/,如果不加/就是相对路径。容易出问题;
	 *  forward:/hello.jsp
	 *  forward:前缀的转发,不会由我们配置的视图解析器拼串
	 * 	
	 */
	@RequestMapping("/handle01")
	public String handle01(){
		System.out.println("handle01");
		return "forward:/hello.jsp";
	}
	
	@RequestMapping("/handle02")
	public String handle02(){
		System.out.println("handle02");
		//转发到handle01,handle01再转发到/hello.jsp
		return "forward:/handle01"; 
	}
2)redirect:重定向

重定向 redirect:重定向的路径
有前缀的转发和重定向操作,配置的视图解析器就不会进行拼串

/**
	 * 重定向到hello.jsp页面
	 * 有前缀的转发和重定向操作,配置的视图解析器就不会进行拼串;
	 * 
	 * 转发	forward:转发的路径
	 * 重定向 redirect:重定向的路径
	 * 		/hello.jsp:代表就是从当前项目下开始;SpringMVC会为路径自动的拼接上项目名
	 * 
	 * 		原生的Servlet重定向/路径需要加上项目名才能成功
	 * 		response.sendRedirect("/hello.jsp")
	 * @return
	 */
	@RequestMapping("/handle03")
	public String handle03(){
		System.out.println("handle03....");
		return "redirect:/hello.jsp";
	}
	
	@RequestMapping("/handle04")
	public String handle04(){
		System.out.println("handle04...");
		//先重定向到handle03再重定向到/hello.jsp
		return "redirect:/handle03";
	}

视图解析器流程源码

  • 方法执行后的返回值会作为页面地址参考,转发或者重定向到页面
  • 视图解析器可能会进行页面地址的拼串;
  • 任何方法的返回值,最终都会被包装成ModelAndView对象
    在这里插入图片描述
(1)根据当前请求找到哪个类能处理

在这里插入图片描述

(2)找到适配器

在这里插入图片描述

(3)目标方法执行

在这里插入图片描述
|| step into
|| 目标方法执行有返回值
\/
在这里插入图片描述
|| step into
|| 真正执行目标方法
\/
在这里插入图片描述
||
|| 目标方法执行后的返回值
\/
在这里插入图片描述
|| 目标方法的返回值会封装成ModelAndView
|| 视图名是…/…/hello
\/
在这里插入图片描述
|| 目标方法执行后返回的ModelAndView
|| 有视图名和模型数据
\/
在这里插入图片描述
|| 传入ModelAndView,返回页面
|| 即视图渲染
\/
在这里插入图片描述

(4)视图渲染流程

将域中的数据在页面展示,页面就是用来渲染模型数据的

(一)根据ModelAndView获取视图对象

在这里插入图片描述
|| step into
||
\/
在这里插入图片描述
|| 如果ModelAndView不为空或者没有被清理
|| 执行render(mv, request, response)方法
\/
在这里插入图片描述
|| step into
|| render(mv, request, response)渲染页面
|| request, response可以转发或重定向,mv有页面地址和模型数据
\/
在这里插入图片描述
|| View与ViewResolver
||
\/
在这里插入图片描述

|| ViewResolver接口,传入视图名,得到View对象
||
\/
在这里插入图片描述
|| step into
||
\/
在这里插入图片描述
|| 依次step into
|| mv.getViewName()、getModelInternal、resolveViewName
\/
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
|| viewResolvers是九大组件,如果自己配就用自己的,没有就用默认的
||
\/
在这里插入图片描述
|| 有就用自己配的viewResolver
||
\/
在这里插入图片描述
|| 没有配viewResolvers
|| viewResolvers初始化的时候,默认也是internalResourceViewResolver
\/
在这里插入图片描述
|| 初始化先找到IOC中的detectAllViewResolver
|| detectAllViewResolver找到IOC中的所有ViewResolver
|| 如果detectAllViewResolver有找到自定义配的ViewResolver就用自己配的
\/
在这里插入图片描述
|| 如果detectAllViewResolver没找到自定义配的ViewResolver
|| getDefaultStrategies获取默认的internalResourceViewResolver
\/
在这里插入图片描述

|| springmvc–> DispatcherServlet–> DispatcherServlet.propertes
||
\/
在这里插入图片描述
在这里插入图片描述
|| 调用View view = viewResolver.resolveViewName(viewName, locale)
|| 获取视图对象
\/
在这里插入图片描述
|| step into resolveViewName
||
\/
在这里插入图片描述
|| view对象第一次运行缓存为null
||
\/
在这里插入图片描述
|| view缓存为null
|| 创建一个view对象
\/
在这里插入图片描述
|| step into createView()
|| 分为有前缀redirect和forward、没有前缀
\/
在这里插入图片描述
在这里插入图片描述
没有前缀调用父类构造器默认创建一个view对象
在这里插入图片描述
在这里插入图片描述
|| 因为View是一个接口,所以有以下内容
||
\/
在这里插入图片描述
在这里插入图片描述
|| viewResolver也是接口
||
\/
在这里插入图片描述

|| view对象创建完成,得到一个InternalResourceView
||
\/
在这里插入图片描述
|| view对象创建完成,缓存起来,下次不用创建
||
\/
在这里插入图片描述

//获取view对象源码
@Override
    protected View createView(String viewName, Locale locale) throws Exception {
        // If this resolver is not supposed to handle the given view,
        // return null to pass on to the next resolver in the chain.
        if (!canHandle(viewName, locale)) {
            return null;
        }
        // Check for special "redirect:" prefix.
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
            return applyLifecycleMethods(viewName, view);
        }
        // Check for special "forward:" prefix.
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            return new InternalResourceView(forwardUrl);
        }
        // Else fall back to superclass implementation: calling loadView.
        //如果没有前缀就使用父类默认创建一个View;
        return super.createView(viewName, locale);
    }
(二)用View对象渲染页面

1、视图解析器得到View对象的流程是,所有配置的视图解析器都来尝试根据视图名(返回值)得到View(视图)对象;如果能得到就返回,得不到就换下一个视图解析器

在这里插入图片描述
2、调用View对象的render方法,视图解析器只是为了得到视图对象;视图对象才能真正的转发(将模型数据全部放在请求域中)或者重定向到页面,视图对象才能真正的渲染视图

在这里插入图片描述
|| step into
|| createMergedOutputModel(model, request, response),创建一个合并输出模型
|| renderMergedOutputModel(mergedModel, request, response)渲染要给页面输出的所有数据
\/
在这里插入图片描述
|| step into
|| InternalResourceView实现了这个方法renderMergedOutputModel
\/

@Override
    protected void renderMergedOutputModel(
            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

        // Determine which request handle to expose to the RequestDispatcher.
        HttpServletRequest requestToExpose = getRequestToExpose(request);

        // Expose the model object as request attributes.
        //将隐含模型中的数据放在请求域中
        exposeModelAsRequestAttributes(model, requestToExpose);

        // Expose helpers as request attributes, if any.
        exposeHelpers(requestToExpose);

        // Determine the path for the request dispatcher.
        String dispatcherPath = prepareForRendering(requestToExpose, response);

        // Obtain a RequestDispatcher for the target resource (typically a JSP).
        RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
        if (rd == null) {
            throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                    "]: Check that the corresponding file exists within your web application archive!");
        }

        // If already included or response already committed, perform include, else forward.
        if (useInclude(requestToExpose, response)) {
            response.setContentType(getContentType());
            if (logger.isDebugEnabled()) {
                logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
            }
            rd.include(requestToExpose, response);
        }

        else {
            // Note: The forwarded resource is supposed to determine the content type itself.
            if (logger.isDebugEnabled()) {
                logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
            }
            rd.forward(requestToExpose, response);
        }
    }

|| step into
|| exposeModelAsRequestAttributes(model, requestToExpose);将隐含模型中的数据放在请求域中
\/

protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
        for (Map.Entry<String, Object> entry : model.entrySet()) {
            String modelName = entry.getKey();
            Object modelValue = entry.getValue();
            if (modelValue != null) {
                request.setAttribute(modelName, modelValue);
                if (logger.isDebugEnabled()) {
                    logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                            "] to request in view with name '" + getBeanName() + "'");
                }
            }
            else {
                request.removeAttribute(modelName);
                if (logger.isDebugEnabled()) {
                    logger.debug("Removed model object '" + modelName +
                            "' from request in view with name '" + getBeanName() + "'");
                }
            }
        }
    }

在这里插入图片描述
|| 拿到转发器rd
||
\/
在这里插入图片描述
|| 用转发器rd转发
||
\/
在这里插入图片描述

视图解析相关概念理解

(1)常用的视图对象(通过视图解析器获取)

在这里插入图片描述

(2)视图解析器

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

JSTView支持便捷国际化功能

1、导包导入了jstl的时候会自动创建为一个jstlView;可以快速方便的支持国际化功能;
2、支持快速国际化的方法;
     1)、传统javaWeb国际化步骤;
               1)、得得到一个Locale对象;
               2)、使用ResourceBundle绑定国际化资源文件;
               3)、使用ResourceBundle.getString("key");获取到国际化配置文件中的值;
               4)、web页面的国际化,fmt标签库来做;
                         <fmt:setLocale>
                         <fmt:setBundle >   
                         <fmt:message>
     2)、JstlView1)、让Spring管理国际化资源
               2)、导入JSTL包,用<fmt:message>取国际化信息;

导入JSTL包自动创建为一个jstlView,不再使用默认的internalResourceView
在这里插入图片描述

在这里插入图片描述

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/pages/"></property>
		<property name="suffix" value=".jsp"></property>
		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
</bean>

在这里插入图片描述
在这里插入图片描述

(1)配置JSTL
//springDispatcherServlet-servlet.xml
<!--SpringMVC管理国际化资源文件,配置一个资源文件管理器,id必须是messageSource -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <!--  basename指定基础名-->
    <property name="basename" value="i18n"></property>
</bean>

<!-- 去页面使用<fmt:message -->
<fmt:message key="welcomeinfo"/>
</h1>
<form action="">
    <fmt:message key="username"/>:<input /><br/>
    <fmt:message key="password"/>:<input /><br/>
    <input type="submit" value='<fmt:message key="loginBtn"/>'/>
</form>

在这里插入图片描述
在这里插入图片描述
|| 导入fmt标签
|| 并且使用fmt标签取值
\/
在这里插入图片描述
在这里插入图片描述

|| 为什么id必须是messageSource
||
\/

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)JSTL国际化避雷

在这里插入图片描述
|| 把login.jsp放到项目目录下,跳过了Springmvc的视图解析,
|| 直接把jsp页面发到tomcat,无法获取国际化配置
\/
在这里插入图片描述

自定义视图和视图解析器

视图解析器根据方法的返回值得到视图对象;
多个视图解析器都会尝试能否得到视图对象;
视图对象不同就可以具有不同功能;
(1)处理响应内容乱码

只设置响应的编码格式还不行,还要设置内容格式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)视图解析器优先级

默认是InternalResourceViewResolver索引第一位
在这里插入图片描述
|| InternalResourceViewResolver没有前缀也可以创建视图对象
|| 但不是自定义的
\/
在这里插入图片描述
|| InternalResourceViewResolver拼接创建的view对象
||
\/在这里插入图片描述
|| 404
||
\/
在这里插入图片描述

(3)设置自定义视图解析器和视图

在这里插入图片描述

@Controller
public class MyViewResovlerController {
	
	@RequestMapping("/handleplus")
	public String handleplus(Model model){
		//meinv:/gaoqing  meinv:/dama
		//forward:/login.jsp
		List<String> vname = new ArrayList<String>();
		List<String> imgname = new ArrayList<String>();
		vname.add("佟老师");
		vname.add("飞哥");
		imgname.add("萌萌");
		
		model.addAttribute("video", vname);
		model.addAttribute("imgs", imgname);
		
		return "meinv:/gaoqing";
	}
}
public class MyView implements View{

	/**
	 * 返回的数据的内容类型
	 */
	@Override
	public String getContentType() {
		// TODO Auto-generated method stub
		return "text/html";
	}

	@Override
	public void render(Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		
		System.out.println("之前保存的数据:"+model);
		//设置内容格式
		response.setContentType("text/html");
		List<String> vn = (List<String>) model.get("video");
		response.getWriter().write("哈哈<h1>即将展现精彩内容</h1>");
		for (String string : vn) {
			response.getWriter().write("<a>下载"+string+".avi</a><br/>");
		}
}
public class MyMeiNVViewResolver implements ViewResolver,Ordered{

	private Integer order = 0;
	
	@Override
	public View resolveViewName(String viewName, Locale locale)
			throws Exception {
		//根据视图名返回视图对象
		/**
		 * 	meinv:/gaoqing  meinv:/dama
			forward:/login.jsp
		 */
		if(viewName.startsWith("meinv:")){
			return new MyView();
		}else{
			//如果不能处理返回null即可
			return null;
		}
	}


	@Override
	public int getOrder() {
		// TODO Auto-generated method stub
		return order;
	}
	
	//改变视图解析器的优先级
	public void setOrder(Integer order){
		this.order = order;
	}

}
//springDispatcherServlet-servlet.xml
	<!--自定义的视图解析器    value="1"数字越小优先级越高-->
	<bean class="com.atguigu.view.MyMeiNVViewResolver">
		<property name="order" value="1"></property>
	</bean>

在这里插入图片描述

二、RestfulCRUD

(1)环境搭建分析

web.xml


<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>7.SpringMVC_crud</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<!-- 指定配置文件 -->
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- 字符编码Filter -->
	<filter>
		<filter-name>CharacterEncodingFilter</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>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 支持Rest风格转换的filter -->
	<filter>
		<filter-name>HiddenHttpMethodFilter</filter-name>
		<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>HiddenHttpMethodFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>

springmvc.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

	<context:component-scan base-package="com.atguigu"></context:component-scan>
	
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/pages/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>

	<!-- 默认前端控制器是拦截所有资源(除过jsp),js文件就404了;要js文件的请求是交给tomcat处理的
	http://localhost:8080/7.SpringMVC_crud/scripts/jquery-1.9.1.min.js -->
	<!-- 告诉SpringMVC,自己映射的请求就自己处理,不能处理的请求直接交给tomcat -->
	<!-- 静态资源能访问,动态映射的请求就不行 -->
	<mvc:default-servlet-handler/>
	<!-- springmvc可以保证动态请求和静态请求都能访问 -->
	<mvc:annotation-driven></mvc:annotation-driven>
</beans>

(2)员工列表展示

员工列表展示;查询所有员工;
   员工列表展示:访问index.jsp----直接发送/emps------控制器查询所有员工------放在请求域中-----转发到list页面展示
增删改查的URL地址;       /资源名/资源标识
/emp/1          GET:查询id为1的员工
/emp/1          PUT:更新id为1的员工
/emp/1          DELETE:删除id为1的员工
/emp            POST:新增员工;
/emps           GET:查询所有员工

在这里插入图片描述

@Controller
public class EmployeeController {

	@Autowired
	EmployeeDao employeeDao;
	
	/**
	 * 查询所有员工
	 */
	@RequestMapping("/emps")
	public String getEmps(Model model) {
		Collection<Employee> all = employeeDao.getAll();
		model.addAttribute("emps", all);
		return "list";
	}
}		
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@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>
<%
	pageContext.setAttribute("ctp", request.getContextPath());
%>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>员工列表</title>
<script type="text/javascript" src="${ctp }/scripts/jquery-1.9.1.min.js"></script>
</head>
<body>
	
	<h1>员工列表</h1>
	<table border="1" cellpadding="5" cellspacing="0">
		<tr>
			<th>ID</th>
			<th>lastName</th>
			<th>email</th>
			<th>gender</th>
			<th>departmentName</th>
			<th>EDIT</th>
			<th>DELETE</th>
		</tr>
		<c:forEach items="${emps }" var="emp">
			<tr>
				<td>${emp.id }</td>
				<td>${emp.lastName}</td>
				<td>${emp.email }</td>
				<td>${emp.gender==0?"女":"男" }</td>
				<td>${emp.department.departmentName }</td>
				<td><a href="${ctp }/emp/${emp.id }">edit</a></td>
				<td><a href="${ctp }/emp/${emp.id }" class="delBtn">delete</a></td>
			</tr>
		</c:forEach>
	</table>
</body>

(3)员工添加

员工添加:
   在list页面点击“”员工添加“”----(查询出所有的部门信息要展示在页面)----来到添加页面(add.jsp)--------
   输入员工数据--------点击保存(/emp )------处理器收到员工保存请求(保存员工)--------保存完成以后还是来到列表页面;

在这里插入图片描述

@Controller
public class EmployeeController {

	@Autowired
	DepartmentDao departmentDao;

	/**
	 * 去员工添加页面,去页面之前需要查出所有部门信息,进行展示的
	 */
	@RequestMapping("/toaddpage")
	public String toAddPage(Model model) {
		// 1、先查出所有部门
		Collection<Department> departments = departmentDao.getDepartments();
		// 2、放在请求域中
		model.addAttribute("depts", departments);
		model.addAttribute("employee", new Employee());
		// 3、去添加页面
		return "add";
	}
}	

Springmvc支持的表单方式:
第一种:原生form表单
在这里插入图片描述
在这里插入图片描述

第二种:表单标签
通过 SpringMVC的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显
第一步:添加表单标签库
在这里插入图片描述
第二步:使用表单标签

<form:form action="" >
	<!-- path就是原来html-input的name项:需要写 
		path:
			1)、当做原生的name项
			2)、自动回显隐含模型中某个对象对应的这个属性的值
	-->
	lastName:<form:input path="lastName"/><br/>
	email:<form:input path="email"/><br/>
	gender:<br/>
		男:<form:radiobutton path="gender" value="1"/><br/>
		女:<form:radiobutton path="gender" value="0"/><br/>
	dept:
		<!-- 
		items="":指定要遍历的集合 ;自动遍历;遍历出的每一个元素是一个department对象
		itemLabel="属性名":指定遍历出的这个对象的哪个属性是作为option标签体的值
		itemValue="属性名":指定刚才遍历出来的这个对象的哪个属性是作为要提交 的value值
		 -->
		<form:select path="department.id" 
			items="${depts }" 
			itemLabel="departmentName" 
			itemValue="id"></form:select><br/>
	<input type="submit" value="保存"/>
</form:form>

||
||
\/
在这里插入图片描述
|| 用了表单标签的页面可能会报这个错误
|| 请求域中没有一个command类型的对象,来到页面之前一定要给请求域中放这个对象
\/
在这里插入图片描述
在这里插入图片描述

报错原因:
SpringMVC认为,表单数据中的每一项最终都是要回显的;
path指定的是一个属性;这个属性是从隐含模型(请求域中取出的某个对象中的属性);
path指定的每一个属性,请求域中必须有一个对象,拥有这个属性;
这个对象就是请求域中的command;

在这里插入图片描述
|| command对象直接返回页面
|| 解决:modelAttribute=""
\/
在这里插入图片描述
|| modelAttribute="" 告诉springmvc不要使用command作为key
|| 用指定的key
\/
在这里插入图片描述
在这里插入图片描述

添加员工
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(4)员工修改页面

在这里插入图片描述
点击edit进入员修改页面

//index.jsp
//添加c标签
<%@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>
<%
		//项目绝对路径
		pageContext.setAttribute("ctp", request.getContextPath());
%>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>员工列表</title>
<script type="text/javascript" src="${ctp }/scripts/jquery-1.9.1.min.js"></script>
</head>
<body>
	
	<h1>员工列表</h1>
	<table border="1" cellpadding="5" cellspacing="0">
		<tr>
			<th>ID</th>
			<th>lastName</th>
			<th>email</th>
			<th>gender</th>
			<th>departmentName</th>
			<th>EDIT</th>
			<th>DELETE</th>
		</tr>
		<c:forEach items="${emps }" var="emp">
			<tr>
				<td>${emp.id }</td>
				<td>${emp.lastName}</td>
				<td>${emp.email }</td>
				<td>${emp.gender==0?"女":"男" }</td>
				<td>${emp.department.departmentName }</td>
				//发送请求,请求路径为项目绝对路径/emp/员工id
				<td><a href="${ctp }/emp/${emp.id }">edit</a></td>
			</tr>
		</c:forEach>
	</table>

在这里插入图片描述
|| index.jsp–>controller
|| 根据员工ID,查询出要修改的员工
\/
在这里插入图片描述
|| controller–>edit页面
|| 查询出要修改的员工信息,回显,用户输入更新的信息
\/
在这里插入图片描述
在这里插入图片描述
|| edit.jsp接收用户更新的信息,发送请求到controlller
||
\/

//添加tags-form标签
%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%
	pageContext.setAttribute("ctp", request.getContextPath());
%>
</head>
<body>
<h1>员工修改页面</h1>
<!-- modelAttribute:这个表单的所有内容显示绑定的是请求域中 employee的值-->
<form:form action="${ctp }/emp/${employee.id }" modelAttribute="employee" method="post">
	//隐藏域,声明表单的请求方式为put
	<input type="hidden" name="_method" value="put"/>
	email:<form:input path="email"/><br/>
	gender:&nbsp;&nbsp;&nbsp;
		男:<form:radiobutton path="gender" value="1"/>&nbsp;&nbsp;&nbsp;
		女:<form:radiobutton path="gender" value="0"/><br/>
	dept:
		<form:select path="department.id" items="${depts }"
			itemLabel="departmentName" itemValue="id"></form:select>
			<br/>
	<input type="submit" value="修改"/>

</form:form>

|| edit.jsp–>controller
|| 接收要更新的信息,保存
\/

//修改员工
@RequestMapping(value = "/emp/{id}", method = RequestMethod.PUT)
	public String updateEmp(@ModelAttribute("employee")Employee employee/* ,@PathVariable("id")Integer id 不建议使用@PathVariable获取ID*/) {
		System.out.println("要修改的员工:" + employee); //打印要修改的员工信息
		// xxxx 更新保存二合一;
		employeeDao.save(employee);
		return "redirect:/emps";
	}


@ModelAttribute
	public void myModelAttribute(
			//从请求参数中获取id
			@RequestParam(value = "id", required = false ) Integer id,Model model) {
		if (id != null) {
			//提前查询出对应ID的employee,并且放入隐含模型Model
			Employee employee = employeeDao.get(id);
			model.addAttribute("employee", employee);
		}
		System.out.println("hahha ");
	}

(5)删除员工

在这里插入图片描述

(一)删除的简单方式

index.jsp
在这里插入图片描述
controller
在这里插入图片描述

(二)引入JQuery文件静态资源访问

list.jsp
在这里插入图片描述

springmvc.xml
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

三、数据绑定

数据绑定原理思想

为什么要数据转换、数据格式化、数据校验?
SpringMVC封装自定义类型对象
javaBean要和页面提交的数据进行一一绑定
1)、页面提交的所有数据都是字符串
2)、Integer age,Date birth;
          employName=zhangsan&age=18&gender=1
          String age = request.getParameter("age");
牵扯到以下操作;
1)、数据绑定期间的数据类型转换String--Integer String--Boolean,xxx
2)、数据绑定期间的数据格式化问题,比如提交的日期进行转换
       birth=2017-12-15----->Date    
       2017/12/15  2017.12.15  2017-12-15
3)、数据校验
       我们提交的数据必须是合法的
       前端校验:js+正则表达式
       后端校验:重要数据也是必须的
               1)、校验成功!数据合法
               2)、校验失败

在这里插入图片描述

* WebDataBinder:数据绑定器负责数据绑定工作
	数据绑定期间产生的类型转换、格式化、数据校验等问题
* validators负责数据校验工作
* bindingResult负责保存以及解析数据绑定期间数据校验产生的错误
* ConversionService组件:负责数据类型的转换以及格式化功能
          ConversionService中有非常多的converter
          不同类型的转换和格式化用它自己的converter

在这里插入图片描述

//ModelAttributeMethodProcessor
public final Object resolveArgument(
            MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest request, WebDataBinderFactory binderFactory)
            throws Exception {
        String name = ModelFactory.getNameForParameter(parameter);
        Object attribute = (mavContainer.containsAttribute(name)) ?
                mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);
        WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
        if (binder.getTarget() != null) {
               将页面提交过来的数据封装到javaBean的属性中
            bindRequestParameters(binder, request);
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors()) {
                if (isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }
        }

自定义类型转换器

1、 实现Converter接口

public class MyStringToEmployeeConverter implements Converter<String, Employee> {

	@Autowired
	DepartmentDao departmentDao;
	
	/**
	 * 自定义的转换规则
	 */
	@Override
	public Employee convert(String source) {
	
		System.out.println("页面提交的将要转换的字符串" + source);
		Employee employee = new Employee();
		if (source.contains("-")) {
			String[] split = source.split("-");
			employee.setLastName(split[0]);
			employee.setEmail(split[1]);
			employee.setGender(Integer.parseInt(split[2]));
			employee.setDepartment(departmentDao.getDepartment(Integer.parseInt(split[3])));
		}
		return employee;
	}
}

2、将这个Converter配置在ConversionService中

<!-- 告诉SpringMVC别用默认的ConversionService,
     用自定义的ConversionService、自定义的Converter -->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <!--converters转换器中添加我们自定义的类型转换器  -->
        <property name="converters">
            <set>
                <bean class="com.atguigu.component.MyStringToEmployeeConverter"></bean>
            </set>
        </property>
    </bean>

3、告诉SpringNMVC使用这个ConversionService

<!-- conversion-service="conversionService":使用我们自己配置的类型转换组件 -->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

4、源码上WebDataBinder上的ConversionService组件就替换了
在这里插入图片描述


ConversionService converters =
    java.lang.Boolean -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@47d21368
    java.lang.Character -> java.lang.Number : org.springframework.core.convert.support.CharacterToNumberFactory@3cdad94b
    java.lang.Character -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@2a65d058
    java.lang.Enum -> java.lang.String : org.springframework.core.convert.support.EnumToStringConverter@5b63b99f
    java.lang.Number -> java.lang.Character : org.springframework.core.convert.support.NumberToCharacterConverter@1fee86c
    java.lang.Number -> java.lang.Number : org.springframework.core.convert.support.NumberToNumberConverterFactory@281e80cc
    java.lang.Number -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@7cd83820
    
    java.lang.String -> com.atguigu.bean.Employee : com.atguigu.component.MyStringToEmployeeConverter@3dc22c7a
    
    java.lang.String -> java.lang.Boolean : org.springframework.core.convert.support.StringToBooleanConverter@4331ec46
    java.lang.String -> java.lang.Character : org.springframework.core.convert.support.StringToCharacterConverter@76a136a0
    java.lang.String -> java.lang.Enum : org.springframework.core.convert.support.StringToEnumConverterFactory@31192e76
    java.lang.String -> java.lang.Number : org.springframework.core.convert.support.StringToNumberConverterFactory@761ba575
    java.lang.String -> java.util.Locale : org.springframework.core.convert.support.StringToLocaleConverter@17fc3d69
    java.lang.String -> java.util.Properties : org.springframework.core.convert.support.StringToPropertiesConverter@38f0c949
    java.lang.String -> java.util.UUID : org.springframework.core.convert.support.StringToUUIDConverter@8784d56
    java.time.ZoneId -> java.util.TimeZone : org.springframework.core.convert.support.ZoneIdToTimeZoneConverter@c0a6ab8
    java.util.Locale -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@406235ad
    java.util.Properties -> java.lang.String : org.springframework.core.convert.support.PropertiesToStringConverter@7e193089
    java.util.TimeZone -> java.time.ZoneId : org.springframework.core.convert.support.TimeZoneToZoneIdConverter@463abd52
    java.util.UUID -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@59e6cea9
    org.springframework.core.convert.support.ArrayToArrayConverter@1a90e2f2
    org.springframework.core.convert.support.ArrayToCollectionConverter@6762ee5d
    org.springframework.core.convert.support.ArrayToObjectConverter@1aae2bbf
    org.springframework.core.convert.support.ArrayToStringConverter@15ee899f
    org.springframework.core.convert.support.ByteBufferConverter@2609c195
    org.springframework.core.convert.support.ByteBufferConverter@2609c195
    org.springframework.core.convert.support.CollectionToArrayConverter@50c81740
    org.springframework.core.convert.support.CollectionToCollectionConverter@75ece48f
    org.springframework.core.convert.support.CollectionToObjectConverter@59d26508
    org.springframework.core.convert.support.CollectionToStringConverter@1960bfcd
    org.springframework.core.convert.support.FallbackObjectToStringConverter@1137fd96
    org.springframework.core.convert.support.IdToEntityConverter@6a196118,org.springframework.core.convert.support.ObjectToObjectConverter@27400a7
    org.springframework.core.convert.support.MapToMapConverter@50cfc69
    org.springframework.core.convert.support.ObjectToArrayConverter@78a37337
    org.springframework.core.convert.support.ObjectToCollectionConverter@3a0b9fce
    org.springframework.core.convert.support.StringToArrayConverter@8747ea2
    org.springframework.core.convert.support.StringToCollectionConverter@324a432d

总结

1ConversionService:是一个接口,它里面有Converter(转换器)进行工作,
    不同数据类型有不同的Converter进行转换,
    实现Converter接口,可以写一个自定义类型的转换器

2ConverterConversionService中的组件
   1)自定义的Converter得放进ConversionService2)将WebDataBinder中的ConversionService设置成我们这个加了自定义类型转换器的ConversionService

<mvc:annotation-driven>标签

(1)<mvc:annotation-driven>的作用

在这里插入图片描述

1、BeanDefinitionParser接口解析各种标签
在这里插入图片描述
2、SpringMVC解析mvc:annotation-driven表签做了哪些事情
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

    private static final boolean jsr303Present = ClassUtils.isPresent(
            "javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static final boolean jaxb2Present =
            ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static final boolean jackson2Present =
            ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
                    ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static final boolean jacksonPresent =
            ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
                    ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    private static boolean romePresent =
            ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);

        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
        parserContext.pushContainingComponent(compDefinition);

        RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);

        RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
        handlerMappingDef.setSource(source);
        handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerMappingDef.getPropertyValues().add("order", 0);
        handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
        if (element.hasAttribute("enable-matrix-variables") || element.hasAttribute("enableMatrixVariables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute(
                    element.hasAttribute("enable-matrix-variables") ? "enable-matrix-variables" : "enableMatrixVariables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }

        RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
        RuntimeBeanReference validator = getValidator(element, source, parserContext);
        RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);

        RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
        bindingDef.setSource(source);
        bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        bindingDef.getPropertyValues().add("conversionService", conversionService);
        bindingDef.getPropertyValues().add("validator", validator);
        bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

        ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
        ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
        ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
        String asyncTimeout = getAsyncTimeout(element, source, parserContext);
        RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext);
        ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
        ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);

        RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
        handlerAdapterDef.setSource(source);
        handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
        handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
        if (element.hasAttribute("ignore-default-model-on-redirect") || element.hasAttribute("ignoreDefaultModelOnRedirect")) {
            Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute(
                    element.hasAttribute("ignore-default-model-on-redirect") ? "ignore-default-model-on-redirect" : "ignoreDefaultModelOnRedirect"));
            handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
        }
        if (argumentResolvers != null) {
            handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
        }
        if (returnValueHandlers != null) {
            handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
        }
        if (asyncTimeout != null) {
            handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
        }
        if (asyncExecutor != null) {
            handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
        }
        handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
        handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
        String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);

        String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
        RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
        uriCompContribDef.setSource(source);
        uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
        uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
        parserContext.getReaderContext().getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef);

        RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
        csInterceptorDef.setSource(source);
        csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
        RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
        mappedCsInterceptorDef.setSource(source);
        mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
        mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
        String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);

        RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
        exceptionHandlerExceptionResolver.setSource(source);
        exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
        exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
        String methodExceptionResolverName =
                parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);

        RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
        responseStatusExceptionResolver.setSource(source);
        responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        responseStatusExceptionResolver.getPropertyValues().add("order", 1);
        String responseStatusExceptionResolverName =
                parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);

        RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
        defaultExceptionResolver.setSource(source);
        defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        defaultExceptionResolver.getPropertyValues().add("order", 2);
        String defaultExceptionResolverName =
                parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);

        parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
        parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));
        parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
        parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));

        // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
        MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

        parserContext.popAndRegisterContainingComponent();

        return null;
    }
(2)<mvc:default-servlet-handler/> 与<mvc:annotation-driven/>
(一)都不加

<mvc:default-servlet-handler/> 与<mvc:annotation-driven/>都没加,动态资源能访问,静态资源不能访问
动态资源(@RequestMapping映射的资源能访问)
静态资源(.html,.js,.img不能访问)

* DefaultAnnotationHandlerMapping中的handlerMap保存了每一个资源的映射信息
* handlerMap没有保存静态资源映射的请求,所以静态不能访问

在这里插入图片描述
DefaultAnnotationHandlerMapping的HandlerAdapter
在这里插入图片描述

(二)只加<mvc:default-servlet-handler/>

加<mvc:default-servlet-handler/>,不加<mvc:annotation-driven/>静态资源可以访问,动态资源不能访问
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
SimpleUrlHandlerMapping的HandlerAdapter
在这里插入图片描述

* 动态不能访问,因为DefaultAnnotationHandlerMapping没有了
* SimpleUrlHandlerMapping替换DefaultAnnotationHandlerMapping
* SimpleUrlHandlerMapping的作用是将所有请求直接交给tomcat
* 静态能访问,因为SimpleUrlHandlerMapping把所有请求都映射给tomcat
(三)都加

<mvc:default-servlet-handler/> 与<mvc:annotation-driven/>都加,动态资源和静态资源都能访问

两个标签都加上有3个HandlerMapping
没有DefaultAnnotationHandlerMapping
有RequestMappingHandlerMapping 动态资源可以访问

RequestMappingHandlerMapping的handleMethods属性保存了每一个请求用哪个方法来处理

在这里插入图片描述
RequestMappingHandlerMapping的HandlerAdapter
在这里插入图片描述

日期格式化

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在bean添加注解,声明数据格式
在这里插入图片描述
在这里插入图片描述

用ConversionServiceFactoryBean创建的ConversionService组件是没有格式化器存在的
默认的WebDateBinder有格式化

在这里插入图片描述
在这里插入图片描述
FormattingConversionServiceFactoryBean与ConversionServiceFactoryBean内部区别
在这里插入图片描述
在这里插入图片描述

希望自定义类型转换器,既具有类型转换也有格式化功能,就使用FormattingConversionServiceFactoryBean来注册自定义的converter
在这里插入图片描述

<!-- 以后写自定义类型转换器的时候,就使用FormattingConversionServiceFactoryBean来注册;
	既具有类型转换还有格式化功能 -->
	<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
		<!--converters转换器中添加我们自定义的类型转换器  -->
		<property name="converters">
			<set>
				<bean class="com.atguigu.component.MyStringToEmployeeConverter"></bean>
			</set>
		</property>
	</bean>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值