8.SpringMVC

springMVC配置过程 HelloWorld

1.创建Maven项目
(1).导入SpringMVC的spring-webmvc的jar包
(2).将SpringMVC的applicationController.xml的配置文件
复制进来 更改配置文件代码如下:

<context:component-scan base-package=“cn.tedu.spring”>
</context:component-scan>

	<!-- 视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<!-- 前缀 -->
		<property  name="prefix" value="/WEB-INF/"></property>
		<!-- 后缀 -->
		<property name="suffix" value=".jsp"></property>
	</bean> 
	
(3).添加Tomcat运行环境

2.配置DispatcherServlet
(1)在web.xml包中添加配置

SpringMVC
org.springframework.web.servlet.DispatcherServlet

contextConfigLocation
classpath:spring.xml

1

<servlet-mapping>
	<servlet-name>SpringMVC</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>

(2)创建cn.tedu.spring类,并自定义构造方法
@Component
public class User {
	public User() {
		System.out.println("初始化User对象。");
	}
}

3.通过控制器接收请求
自定义控制器类:cn.tedu.spring.HelloController,并在类上面添加注解
@Controller
标准格式:
@Controller
public class HelloController {
}
应用例子:
@Controller(“hello.do”)
public String HelloController {
System.out.println(“HelloController”);
return “hello”
}
4.创建.jsp页面。
5.最后通过hello.do访问页面。

1. 接收请求参数

1.1. (不推荐) 使用HttpServletRequest

在处理请求的方法的参数列表中添加HttpServletRequest参数,然后,在处理过程中,调用requestgetParameter()方法即可获取各请求参数的值:

@RequestMapping("handle_register.do")
public String handleRegister(HttpServletRequest request) {
	System.out.println("UserController.handleRegister()");
	String username = request.getParameter("username");
	String password = request.getParameter("password");
	Integer age = Integer.valueOf(request.getParameter("age"));
	String phone = request.getParameter("phone");
	String email = request.getParameter("email");
	System.out.println("\tusername=" + username);
	System.out.println("\tpassword=" + password);
	System.out.println("\tage=" + age);
	System.out.println("\tphone=" + phone);
	System.out.println("\temail=" + email);
	return null;
}

以上做法的不足有:

  1. 如果期望的数据类型不是String,需要自行转换!

  2. 获取数据的操作比较繁琐;

  3. 不便于执行单元测试。

1.2. (推荐) 将请求参数设计为处理请求的方法的参数

当需要获取请求参数时,直接将它们添加到处理请求的方法的参数列表中即可:

@RequestMapping("handle_register.do")
public String handleRegister(
		String username, String password, 
		Integer age, String phone, String email) {
	System.out.println("UserController.handleRegister()");
	System.out.println("\tusername=" + username);
	System.out.println("\tpassword=" + password);
	System.out.println("\tage=" + (age + 1));
	System.out.println("\tphone=" + phone);
	System.out.println("\temail=" + email);
	return null;
}

这种操作要求请求参数的名称与处理请求的方法的参数名称保持一致!如果名称不一致,则处理请求的方法中的参数值将是null

在设计处理请求的方法的参数列表时,各参数的类型可以直接声明为期望的数据类型,例如期望名为age的量是Integer类型,则直接声明为Integer即可,无需在方法中自行转换!

这种做法的缺陷在于:不适合处理过多的请求参数。

1.3. (推荐) 使用封装的类型作为处理请求的方法的参数

当请求参数较多时,可以将这些参数封装到1个自定义的类型中:

public class User {

	private String username;
	private String password;
	private Integer age;
	private String phone;
	private String email;

}

然后,将自定义的数据类型作为处理请求的方法的参数即可:

@RequestMapping("handle_register.do")
public String handleRegister(User user) {
	System.out.println("UserController.handleRegister()");
	System.out.println("\t" + user);
	return null;
}

同样,这种做法仍要求请求参数的名称,与封装的类型中的属性名称保持一致!

这种做法还有1个优点:当请求参数的数量发生变化时,也许处理请求的方法的参数列表可以不用调整,而只调整自定义的封装类型即可。

1.4. 如何选取各种获取请求参数的方式

第1种做法是始终不推荐的,即完全不使用第1种做法。

如果参数的数量较多,应该优先选取第3种做法;如果参数的数量可能发生变化,应该优先选取第3种做法;如果参数的数量较少且固定,应该优先选取第2种做法。

另外,第2种做法和第3种做法可以混合在一起使用!

2. 重定向

在处理请求的方法中,返回String类型的结果时,返回值使用redirect:作为前缀,则表示重定向!在redirect:右侧的必须是重定向到的目标的相对定位或绝对定义的URL,例如:

return "redirect:xxx.do";

假设当前用户的注册一定能成功,且注册成功后显示登录页面,则:

@RequestMapping("handle_register.do")
public String handleRegister(User user) {
	System.out.println("UserController.handleRegister()");
	System.out.println("\t" + user);
	return "redirect:login.do";
}

3. 转发数据

3.1. (不推荐) 通过HttpServletRequest参数封装转发的数据

在SpringMVC中,如果方法的返回值类型是String类型,默认转发!

假设在控制器的方法中处理登录时,只有root/1234是正确的用户名和密码,登录成功后重定向到主页,而登录失败,将错误信息转发到**/WEB-INF/error.jsp**错误页面。

当需要转发时,可以在处理请求的方法中添加HttpServletRequest参数,在使用时,调用参数对象的setAttribute()方法即可封装转发的数据,然后,执行转发即可(无需获取转发器等):

@RequestMapping("handle_login.do")
public String handleLogin(String username, String password, HttpServletRequest request) {
	System.out.println("UserController.handleLogin();");
	System.out.println("\tusername=" + username);
	System.out.println("\tpassword=" + password);
	
	// 定义错误提示信息
	String errorMessage;
	// 判断用户名是否正确
	if ("root".equals(username)) {
		// 用户名正确,继续判断密码是否正确
		if ("1234".equals(password)) {
			// 密码也正确,则登录成功,重定向到主页
			return "redirect:index.do";
		} else {
			// 密码错误,先准备好错误提示
			errorMessage = "登录失败!密码错误!";
			// 将错误提示封装到请求对象中
			request.setAttribute("msg", errorMessage);
			// 执行转发
			return "error";
		}
	} else {
		// 用户名错误
		errorMessage = "登录失败!用户名不存在!";
		request.setAttribute("msg", errorMessage);
		return "error";
	}
	
}
3.2. (更不推荐) 使用ModelAndView

可以使用ModelAndView作为处理请求的方法的返回值类型,在该类型的对象中,设置其viewName属性即可确定需要转发到的视图名称,另外还使用了Map<String, ?>类型的数据作为需要转发的数据:

@RequestMapping("handle_login.do")
public ModelAndView handleLogin(String username, String password) {
	System.out.println("UserController.handleLogin();");
	System.out.println("\tusername=" + username);
	System.out.println("\tpassword=" + password);
	
	// 准备返回值对象
	ModelAndView mav;
	// 封装转发的数据的Map
	Map<String, Object> model = new HashMap<String, Object>();
	// 定义错误提示信息
	String errorMessage;
	// 判断用户名是否正确
	if ("root".equals(username)) {
		// 用户名正确,继续判断密码是否正确
		if ("1234".equals(password)) {
			// 密码也正确,则登录成功,重定向到主页
			return null;
		} else {
			// 密码错误,先准备好错误提示
			errorMessage = "登录失败!密码错误!";
			// 将错误提示封装到请求对象中
			model.put("msg", errorMessage);
			mav = new ModelAndView("error", model);
			// 执行转发
			return mav;
		}
	} else {
		// 用户名错误
		errorMessage = "登录失败!用户名不存在!";
		model.put("msg", errorMessage);
		mav = new ModelAndView("error", model);
		return mav;
	}
	
}
3.3. (推荐) 使用ModelMap封装转发的数据

使用方式与使用HttpServletRequest是相同的:

@RequestMapping("handle_login.do")
public String handleLogin(String username, String password, ModelMap modelMap) {
	System.out.println("UserController.handleLogin();");
	System.out.println("\tusername=" + username);
	System.out.println("\tpassword=" + password);
	
	// 定义错误提示信息
	String errorMessage;
	// 判断用户名是否正确
	if ("root".equals(username)) {
		// 用户名正确,继续判断密码是否正确
		if ("1234".equals(password)) {
			// 密码也正确,则登录成功,重定向到主页
			return null;
		} else {
			// 密码错误,先准备好错误提示
			errorMessage = "[ModelMap] 登录失败!密码错误!";
			// 将错误提示封装到请求对象中
			modelMap.addAttribute("msg", errorMessage);
			// 执行转发
			return "error";
		}
	} else {
		// 用户名错误
		errorMessage = "[ModelMap] 登录失败!用户名不存在!";
		modelMap.addAttribute("msg", errorMessage);
		return "error";
	}
	
}

4. 关于@RequestMapping注解

在处理请求的方法之前添加@RequestMapping用于配置请求路径与处理响应的方法之间的映射。

该注解也可以添加在类的声明之前,例如:

@RequestMapping("user")
@Controller
public class UserController {
}

添加在类之前的@RequestMapping注解用于配置请求路径中的层次!将作用于当前类中配置的所有请求路径!原有的例如login.do的请求路径就会变成user/login.do

无论是添加在类之前配置路径,还是在方法之前配置路径,在配置时,路径的左右两侧都可以添加/符号,例如:

@RequestMapping("/user/")

在使用该注解时,对左右两侧的/是没有要求的,例如在类和方法之前分别配置:

user		login.do
/user		/login.do
/user/		/login.do
/user/		login.do

以上各种配置方式都是正确的!在实际使用时,应该保持自己的代码风格统一!

在实际应用中,推荐为每一个类都添加该注解!

配置注解中的value属性或path属性,可以配置请求路径与处理请求的方法的映射,且值可以是数组类型:

@RequestMapping(name="hahaha", path={"reg.do", "register.do"})

可以在注解中配置method属性,以限制某个请求路径的允许使用的若干种请求方式,例如:

@RequestMapping(path="handle_login.do", method=RequestMethod.POST)

或:

@RequestMapping(path="handle_login.do", method={RequestMethod.POST, RequestMethod.GET})

如果使用错误的请求方式发出请求,则会导致405错误:

HTTP Status 405 - Request method 'GET' not supported

5. 关于@RequestParam注解

@RequestParam是添加在请求参数之前的注解!

使用该注解可以解决客户端提交的请求参数名称,与服务器端的处理请求的方法的参数名称不统一的问题:

@RequestParam("uname") String username

当添加以上注解后,默认情况下,该参数就是必须提交的,如果请求中没有提交该参数,就会出现400错误:

HTTP Status 400 - Required String parameter 'uname' is not present

这种表现是由于该属性中的required属性决定的,在该注解的源代码:

boolean required() default true;

所以,如果并不强制要求客户端提交该参数,可以:

@RequestParam(name="uname", required=false)

另外,还可以配置defaultValue属性,用于配置默认值,即当客户端没有提交该请求参数时,视为客户端提交了某个值:

@RequestParam(name="uname", required=false, defaultValue="JSD1902")

当然,在设置defaultValue时,需要显式的将required设置为false

所以,通常@RequestParam注解的使用场景:

  1. 客户端提交的请求参数名称与服务器端处理请求的方法的参数名称不一致时;

  2. 强制要求客户端提交某些参数时;

  3. 需要为某些请求参数设置默认值时。

1. SpringMVC中的拦截器(Interceptor)

1.1. 基本概念

在SpringMVC中的拦截器可以是运行在控制器(Controller)之前的组件,可以设置拦截器应用于哪些请求路径,当发生这些请求时,拦截器会自动执行,在执行过程中,可以对请求相关数据进行判断,选择阻止继续向后执行,或选择放行。

注意:拦截器是一个若干种请求都会经历的执行过程,但是,并不一定需要阻止继续运行,只要是若干种请求都需要做相同的事情,也许每种请求的处理过程都是选择放行,也可以使用拦截器。

1.2. 开发拦截器

首先,所有的拦截器类都必须实现HandlerInterceptor接口,可以自定义LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("LoginInterceptor.preHandle()");
		return false;
	}

	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("LoginInterceptor.postHandle()");
	}

	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("LoginInterceptor.afterCompletion()");
	}

}

在拦截器的3个方法中,只有preHandle()方法是运行在控制器(Controller)之前的,另2个方法是运行在控制器之后的,所以,只有preHandle()具有真正意义的“拦截”功能,该方法的返回值是boolean类型的,当返回true时表示放行,返回false时将阻止继续向后执行,即控制器并不会被执行:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {
	System.out.println("LoginInterceptor.preHandle()");
	// 获取Session
	HttpSession session = request.getSession();
	// 检查Session中是否有登录信息
	if (session.getAttribute("uid") == null) {
		// 没有登录信息,重定向到登录页
		response.sendRedirect("../user/login.do");
		// 执行拦截
		return false;
	} 
	// 放行
	return true;
}

注意:即使已经决定了重定向,还是需要return false;否则处理流程会继续向执行,控制器中的方法还是会被调用,达不到阻止运行的效果!

所有的拦截器都需要在Spring的配置文件中进行配置,在SpringMVC框架中,允许使用若干个拦截器,形成拦截器链,即某个请求可能需要经过多个拦截器,仅当每个拦截器都放行时,才会执行控制器中的方法!在配置文件中,配置的先后顺序决定了多个拦截器的执行顺序:

<!-- 配置拦截器链 -->
<mvc:interceptors>
	<!-- 配置第1个拦截器 -->
	<mvc:interceptor>
		<!-- 拦截路径 -->
		<mvc:mapping path="/main/index.do"/>
		<!-- 拦截器类 -->
		<bean class="cn.tedu.spring.LoginInterceptor"></bean>
	</mvc:interceptor>
	<!-- 配置第2个拦截器 -->
</mvc:interceptors>

在配置每个拦截器时,允许使用若干个<mvc:mapping>节点以配置若干个拦截路径,例如:

<mvc:interceptor>
	<!-- 拦截路径 -->
	<mvc:mapping path="/main/index.do"/>
	<mvc:mapping path="/user/password.do"/>
	<mvc:mapping path="/user/info.do"/>
	<mvc:mapping path="/user/handle_password.do"/>
	<mvc:mapping path="/user/handle_info.do"/>
	<!-- 拦截器类 -->
	<bean class="cn.tedu.spring.LoginInterceptor"></bean>
</mvc:interceptor>

在配置路径,还可以使用*作为通配符,例如:

<mvc:interceptor>
	<!-- 拦截路径 -->
	<mvc:mapping path="/main/*"/>
	<!-- 拦截器类 -->
	<bean class="cn.tedu.spring.LoginInterceptor"></bean>
</mvc:interceptor>

在使用*作为通配符时,需要注意,1个星号只能匹配1层路径,例如/main/*可以匹配上/main/index.do,也可以匹配/main/hello.do,但是,不可以匹配上/main/a/index.do

如果一定要匹配若干层路径,必须使用2个星号,例如配置为/main/**,可以匹配上/main/index.do,也可以匹配/main/a/hello.do,甚至可以匹配/main/a/b/c/d/hello.do,即无视路径中后续的层级。

所以,可以配置为:

<mvc:interceptor>
	<!-- 拦截路径 -->
	<mvc:mapping path="/main/**"/>
	<mvc:mapping path="/user/**"/>
	<!-- 拦截器类 -->
	<bean class="cn.tedu.spring.LoginInterceptor"></bean>
</mvc:interceptor>

如果通配符匹配的路径过多,需要从中去除某些请求路径,还可以添加例外:

<mvc:interceptor>
	<!-- 拦截路径:黑名单 -->
	<mvc:mapping path="/main/**"/>
	<mvc:mapping path="/user/**"/>
	<!-- 例外路径:白名单 -->
	<mvc:exclude-mapping path="/user/reg.do" />
	<mvc:exclude-mapping path="/user/handle_reg.do" />
	<mvc:exclude-mapping path="/user/login.do" />
	<mvc:exclude-mapping path="/user/handle_login.do" />
	<!-- 拦截器类 -->
	<bean class="cn.tedu.spring.LoginInterceptor"></bean>
</mvc:interceptor>

也就是说,凡/user/下的路径都会经过该拦截器,但是,/user/login.do是不被处理的!添加到“例外”中的路径,在请求时,并不是拦截器直接放行,而是拦截器根本就不执行!

2. 请求参数的乱码解决方案

<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>
</filter>

<filter-mapping>
	<filter-name>CharacterEncodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

3. 在SpringMVC中统一处理异常

在Java中,异常的体系结构:

Throwable
	Error
		OutOfMemoryError
	Exception
		SQLException
		IOException
			FileNotFoundException
		RuntimeException
			NullPointerException
			ClassCastException
			NumberFormatException
			IndexOutOfBoundsException
				ArrayIndexOutOfBoundsException
				StringIndexOutOfBoundsException

Exception中,除了RuntimeException及其子孙类异常,其它的异常都是必须通过语法进行处理的!处理方式可以是try...catch进行捕获并处理,也可以是在方法的签名中添加throws声明抛出。

其实,处理异常的本质应该是:给予用户某些提示,告之操作失败的原因,避免用户再次提交错误的数据,另外,也可能在处理过程中,对已经发生的错误尽可能的补救,例如关闭某些已经打开的流对象。

某一种异常在项目的多个请求处理过程中,都可能出现,在SpringMVC中,提供了统一处理异常的做法:将@ExceptionHandler添加在自定义的用于处理异常的方法之前,该自定义方法应该:

  1. 使用public权限;

  2. 返回值的设计原则与处理请求的方法的相同;

  3. 方法名称可以自由定义;

  4. 方法中必须添加异常类型的参数,另外还可以添加例如HttpServletRequest等参数,但不可以添加其它参数。

例如:

@ExceptionHandler({IndexOutOfBoundsException.class, NullPointerException.class})
public String handleException(Throwable ex, HttpServletRequest request) {
	String errorMessage = null;
	if (ex instanceof NullPointerException) {
		errorMessage = "错误:请提交用户名!";
	} else if (ex instanceof IndexOutOfBoundsException) {
		errorMessage = "错误:使用的索引超出了界限!";
	}
	request.setAttribute("msg", errorMessage);
	return "error";
}

@ExceptionHandler注解中,可以配置需要处理的异常的种类,当配置后,仅当指定的异常出现时,才会调用匹配的方法进行处理,而其它异常是不予处理的!如果没有配置需要处理哪些异常,则任何异常出现都会进行处理!

在处理时,@ExceptionHandler只能作用于当前控制器类!

4. SpringMVC小结

  1. 记住SpringMVC的执行流程图;

  2. 掌握@RequestMapping的使用;

  3. 掌握@RequestParam的使用;

  4. 掌握获取请求参数的方式;

  5. 掌握转发数据与重定向的做法;

  6. 掌握拦截器的使用;

  7. 掌握处理异常的做法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员西柚柚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值