SpringMVC使用小结

SpringMVC框架

1. 作用

解决了V-C的交互问题,即视图与控制器的交互问题。

在原生的Java EE技术中,使用Servlet作为项目中的控制器,用于接收用户的请求,并给予响应结果。这种做法最大的问题在于:在常规做法中,每个Servlet对应1个请求路径,例如LoginServlet处理login.do的请求,而RegisterServlet处理register.do的请求,所以,会导致Servlet数量太多,不便于管理(无论从源文件的数量,还是从配置文件的内容),且对象太多进而占用大量内存空间的问题!并且,在请求的处理过程中,还存在许多操作不简便的做法!

1.2. SpringMVC核心组件

  1. DispatcherServlet

前端控制器,主要职责是接收所有请求(根据配置文件来决定),并将请求转发给对应的控制器,接收控制器的处理结果,确定最终由哪个视图完成响应!

  1. HandlerMapping

处理请求路径与控制器的映射关系。

  1. Controller

实际处理请求的组件,例如接收请求参数,决定最终是转发或重定向的方式来响应。

  1. ModelAndView

控制器的处理结果,其中的Model表示转发的数据(如果是重定向,则Model没有意义),而View表示最终负责响应的视图组件的名称。

  1. ViewResolver

根据视图组件的名称,确定具体使用的是哪个视图组件。

3.3. 【推荐】使用ModelMap封装需要转发的数据

使用ModelMap的流程与使用HttpServletRequest完全相同,即:方法的返回值依然使用String类型,在方法中声明该参数,然后在方法体中直接封装数据,最后,返回视图名:

@RequestMapping("handle_reg.do")
public String handleReg(String username, ModelMap modelMap) {
	modelMap.addAttribute("msg", "[3] 您输入的用户名" + username + "已经被占用!");
	return "error";
}

3.4. 小结

在SpringMVC中,转发数据共有3种做法,第1种使用HttpServletRequest的做法简单直接,但是,并不推荐这样处理,主要是因为框架已经帮我们处理了request需要执行的任务,而我们在编写代码时应该尽量不干预框架的处理过程,第2种使用ModelAndView,是框架的核心处理方式,但是,因为使用方式过于麻烦,所以,也不推荐这样使用,第3种使用ModelMap,使用简洁,推荐使用。

3.5. 附:重定向

在SpringMVC中,当需要重定向时,首先,应该保证处理请求的方法的返回值是String类型(与转发一样),然后,返回值使用redirect:作为前缀即可,例如:

@RequestMapping("handle_reg.do")
public String handleReg() {
	// 假设注册成功,需要登录
	return "redirect:login.do";
}

需要注意的是:在redirect:右侧的不是视图名,而是重定向的目标的路径,可以是绝对路径,也可以是相对路径。
当处理的请求的返回值类型是String时,如果返回值使用redirect:作为前缀,是重定向,否则,是转发!

4. 关于@RequestMapping注解

通过配置@RequestMapping,可以绑定请求路径与处理请求的方法,例如:

@RequestMapping("login.do")
public String showLogin() { ...

即:通过以上配置,当接收到login.do请求时,SpringMVC会自动调用showLogin()方法。

除了配置请求路径以外,使用@RequestMapping还可以限制请求方式,即某个路径可以设置为只允许POST请求,而不接收GET请求!

【GET】会将请求的参数与值体现在URL中;请求的参数与值会受到URL长度限制,不适用于传递大量的数据;

【POST】请求的参数与值不会体现在URL中;可以传递大量的数据;

【选取】请求的参数与值涉及隐私(例如密码)则必须使用POST;数据量可能较大时必须使用POST;需要共享URL且其中包含参数时必须使用GET;支持页面刷新必须使用GET。

【复杂度】如果要发出POST请求,只能通过<form>中的<input type="submit" /><button />,或者通过JS技术,否则,在Web领域无法发出POST请求,而这2种方式也都可以用于发出GET请求,除此以外,直接在浏览器中输入某个URL发出的也是GET请求,总的来说,发GET请求要简单得多。

【小结】参考以上“选取”原则,选择请求方式,如果两者均可,则使用GET即可。

@RequestMapping中配置method属性可以限制请求类型:

@RequestMapping(value="handle_reg.do",method=RequestMethod.POST)
public String handleReg() {
}

例如以上代码限制了handle_reg.do必须通过POST方式来请求,如果使用GET方式,则会返回405错误!

只有需要限定请求方式时,才需要显式的配置value=“handle_reg.do”,否则,直接将"handle_reg.do"配置在注解中即可!

小结:关于@RequestMapping注解,主要作用是配置请求路径,推荐在控制器类和处理请求的方法之前都添加该注解,类之前的注解是用于配置请求路径中的层次,方法之前的注解是用于配置请求的资源,关于路径的配置是该属性的value属性,如果只配置请求路径,可以不用显式的声明这是配置value属性,而是直接把值写出来即可,例如不需要写成@RequestMapping(values="login.do"),而可以直接写成@RequestMapping("login.do"),在配置路径时,推荐使用/作为第1个字符,例如@RequestMapping("/login.do"),如果还需要限制请求方式,则必须显式的声明路径为value属性的值,并且添加配置method属性,例如:@RequestMapping(value="handle_reg.do", method=RequestMethod.POST)

5. 关于@RequestParam注解

使用@RequestParam注解,可以解决请求参数名称与处理请求的方法的参数名称不一致的问题,例如:

public String handleLogin(
	@RequestParam("name") String username, 
	@RequestParam("password") String password) { ...

则请求参数的名称是name,而处理请求的方法中的参数名称却是username,这是可以正常运行的!

一旦使用了@RequestParam注解,默认情况下,参数就是必须的!例如配置了@RequestParam("passwd") String password后,如果请求中并不存在名为passwd的参数,则会出现400错误:

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

没有提交名为passwd的参数,与提交了空值,是两码事!即:如果提交了passwd参数却没有值(例如输入框中没有输入值),在服务器将得到空字符串(""),程序并不会出现错误!如果根本就没有提交名为passwd的参数,则会导致400错误!

如果使用了@RequestParam注解,却又不想设置为必须提交该参数,可以:

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

则将根据name去接收参数,如果有值,会正确接收,如果没有(没有提交该名称的参数),则会是null值!

required=false时,意味着可以不必提交该参数,还可以多配置一项defaultValue属性(The default value to use as a fallback when the request parameter value is not provided or empty. Supplying a default value implicitly sets required() to false.),表示**如果请求中没有提交该参数,则默认值是多少!**例如:

@RequestParam(value="passwd", required=false, defaultValue="888999") String password

以上代码表示:希望请求中包含名为passwd的参数,如果有,则值用于方法的String password的参数,如果没有,也不是必须要提供(required=false),并且使用"888999"作为默认值(defaultValue="888999"),即:在这种情况下,String password的值是"888999"

小结:@RequestParam注解是用于处理请求的方法中的参数之前,可以配置3项属性,分别是value表示请求参数名称,required表示请求中是否必须包含该参数,defaultValue表示参数的默认值,当有以上任何一种需求时,都需要配置该注解,即:请求参数名称与处理请求的方法的参数名称不一致;强制必须提交某个参数;为某个参数配置默认值。

1. SpringMVC中的拦截器(Interceptor)

1.1. 作用

拦截器是运行在DispatcherServlet之后,在每个Controller之前的,且运行结果可以选择放行或拦截!

除此以外,拦截器还会运行在Controller之后,关于拦截器,在处理某一个请求时,最多有3次执行!只不过,通常关注最多的是第1次执行,即在Controller之前的那次!

1.2. 基本使用

需要自定义类,例如名为LoginInterceptor,实现HandlerInterceptor接口,重写3个抽象方法:

拦截器需要在Spring的配置文件中进行配置后才可以生效!所以,需要添加配置:

<!-- 配置拦截器链 -->
<mvc:interceptors>
	<!-- 配置第1个拦截器 -->
	<mvc:interceptor>
		<!-- 指定拦截路径,不在拦截路径之内的将不予处理,即拦截器根本就不运行 -->
		<mvc:mapping path="/user/info.do"/>
		<mvc:mapping path="/user/password.do"/>
		<!-- 指定拦截器类 -->
		<bean class="cn.tedu.spring.interceptor.LoginInterceptor" />
	</mvc:interceptor>
</mvc:interceptors>

在拦截器类中,运行在Controller之前的是preHandle()方法,该方法返回true表示放行,返回false表示拦截!

拦截器需要在Spring的配置文件中进行配置后才可以生效!所以,需要添加配置:

<!-- 配置拦截器链 -->
<mvc:interceptors>
	<!-- 配置第1个拦截器 -->
	<mvc:interceptor>
		<!-- 指定拦截路径,不在拦截路径之内的将不予处理,即拦截器根本就不运行 -->
		<mvc:mapping path="/user/info.do"/>
		<mvc:mapping path="/user/password.do"/>
		<!-- 指定拦截器类 -->
		<bean class="cn.tedu.spring.interceptor.LoginInterceptor" />
	</mvc:interceptor>
</mvc:interceptors>

在拦截器类中,运行在Controller之前的是preHandle()方法,该方法返回true表示放行,返回false表示拦截!

对于登录拦截器而言,可以判断Session中的用户数据,如果数据存在,视为已登录,可直接放行,如果没有数据,则视为没登录,需要重定向,并拦截:

public boolean preHandle(
		HttpServletRequest request, 
		HttpServletResponse response, Object handler)
		throws Exception {
	System.out.println("LoginInterceptor.preHandle()");
	// 判断Session中的数据,得知是否登录
	HttpSession session = request.getSession();
	if (session.getAttribute("username") == null) {
		// Session中没有用户名,即:没有登录,则:重定向,并拦截
		response.sendRedirect("../user/login.do");
		// 返回false表示拦截
		return false;
	}
	// 返回true表示放行
	return true;
}

在配置拦截器时,根节点是<mvc:interceptors>,用于配置拦截器链,即任何一个SpringMVC项目,都可以有若干个拦截器,从而形成拦截器链,如果某个请求符合多个拦截器的拦截配置,则会依次被各拦截器进行处理,任何一个拦截,都会导致后续不再将请求交给Controller去处理!

<mvc:interceptors>(有s)节点子级,可以配置多个<mvc:interceptor>(没有s)子级节点,表示多个拦截器,拦截器链的执行顺序取决于这里各节点的配置先后顺序!

<mvc:interceptor>中,<bean>节点用于指定拦截器类;<mvc:mapping>节点,用于配置拦截的请求路径,每个拦截器可以配置多个该节点,并且,在配置时,支持使用星号*作为通配符,例如:<mvc:mapping path="/user/*"/>,为了避免拦截范围过大,可以通过<mvc:exclude-mapping />配置排除在外的请求路径,可以理解为白名单,该节点也可以配置多个:

<!-- 配置拦截器链 -->
<mvc:interceptors>
	<!-- 配置第1个拦截器 -->
	<mvc:interceptor>
		<!-- 指定拦截路径,不在拦截路径之内的将不予处理,即拦截器根本就不运行 -->
		<mvc:mapping path="/user/*" />
		<!-- 指定白名单,列举的请求路径将不予处理,即拦截器根本就不运行 -->
		<mvc:exclude-mapping path="/user/reg.do" />
		<mvc:exclude-mapping path="/user/login.do" />
		<mvc:exclude-mapping path="/user/handle_reg.do" />
		<mvc:exclude-mapping path="/user/handle_login.do" />
		<!-- 指定拦截器类 -->
		<bean class="cn.tedu.spring.interceptor.LoginInterceptor" />
	</mvc:interceptor>
</mvc:interceptors>

注意:以上配置黑名单或白名单时,都可以使用星号*作为通配符,但是,它只能匹配1层路径(或者说只能通配任意资源)!例如配置的是/user/*,可以匹配/user/reg.do/user/login.do,却无法匹配到/user/a/reg.do/user/a/b/login.do这样的

2.2. 解决控制器中接收请求参数的乱码

通常,在Java EE项目中,解决问题的方式是:

request.setCharacterEncoding("utf-8");

由于Controller是运行在DispatcherServlet之后的,在Controller内部再执行更改编码格式已经晚了,事实上SpringMVC框架在DispatcherServlet之前就存在CharacterEncodingFilter可以确定请求与响应的编码格式,所以,在SpringMVC中,无法通过ControllerInterceptor来解决请求和响应的乱码问题。

在SpringMVC框架的CharacterEncodingFilter中,把使用的字符编码设计为变量,可以在web.xml中添加配置加以应用,来统一设置编码:

<!-- 配置字符编码过滤器 -->
<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>

2.3. 拦截器与过滤器有什么区别

拦截器是Interceptor,过滤器是Filter;

拦截器是SpringMVC中的组件,过滤器是Java EE中的组件;

拦截器是配置在Spring的配置文件中的,过滤器是配置在web.xml中的;

拦截器是运行在DispatcherServlet之后、Controller之前的,且在Controller执行完后还会调用2个方法,而过滤器是运行在所有的Servlet之前的;

拦截器的配置非常灵活,可以配置多项黑名单,也可以配置多项白名单,过滤器的配置非常单一,只能配置1项过滤路径;

拦截器与过滤器也有很多相似之处,例如:都可拒绝掉某些访问,也可以选择放行;都可以形成链。

相比之下,在一个使用SpringMVC框架的项目中,拦截器会比过滤器要好用一些,但是,由于执行时间节点的原因,它并不能完全取代过滤器!

3. 异常

3.1. 基本概念

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

Throwable
	Error
		OutOfMemoryError
	Exception
		IOException
			FileNotFoundException
		SQLException
		UnsupportedEncodingException
		RuntimeException
			NullPointerException
			ClassCastException
			ArithmeticException
			IllegalArgumentException
			IndexOutOfBoundsException
				ArrayIndexOutOfBoundsException

在这些异常中,RuntimeException及其子孙类异常,在Java语法中并不要求必须处理!主要的原因有:这些异常出现的频率可能非常高,如果一定要处理,例如try...catch,则几乎所有的代码都需要放在try代码块中!并且,这些异常是可以杜绝的异常,通过严谨的编程,可以保证这些异常绝对不会出现!

处理异常有2种方式:使用try...catch处理异常,或者使用throw抛出异常对象,并且在方法的声明中使用throws语法声明抛出!

通常,异常都是必须处理的,如果没有处理异常,会导致异常不断向上抛出,最终,Java EE中的异常会由Tomcat来处理,会把跟踪日志显示在页面中!而这些跟踪日志是普通用户看不懂的,对于专业人士而言,还可能分析出项目中的一些内容,对于后续解决问题也没有任何帮助,所以,从开发原则上来说,必须处理异常!

3.2 处理异常-SimpleMappingExceptionResolver

RuntimeException是从语法上强制要求处理的,所以,每次调用了抛出异常的方法,就必须try...catch或继续声明抛出!

RuntimeException及其子孙类异常并不从语法上强制要求处理,但是,一旦出现,会项目的安全、用户体验等各方面都会产生负面影响!

SpringMVC提供了统一处理异常的方式:使用SimpleMappingExceptionResolver类,该类通过private Properties exceptionMappings;属性来配置异常种类与转发到的页面的对应关系,即每一种异常都可以有一个与之对应的页面,一旦项目中出现配置过的异常,就会自动转发到对应的页面来提示错误信息!

<!-- 配置统一处理异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="exceptionMappings">
		<props>
			<prop key="java.lang.RuntimeException">runtime</prop>
			<prop key="java.lang.NullPointerException">null</prop>
			<prop key="java.lang.ArrayIndexOutOfBoundsException">index</prop>
		</props>
	</property>
</bean>

这种方式处理异常非常的简单,但是,也有一个比较大的问题:无法提示更详细的信息!例如出现ArrayIndexOutOfBoundsException时,其实,异常对象中还封装了详细的错误信息,即:越界值是多少

3.3 处理异常-@ExceptionHandler

可以在控制器类中自定义一个处理异常的方法,在方法之前添加@ExceptionHandler,然后,凡是约定范围内的异常,都可以由该方法来决定如何处理:

@ExceptionHandler
public String handleException(Exception e) {
	System.out.println(
		"ExceptionController.handleException()");
	
	if (e instanceof NullPointerException) {
		return "null";
	} else if (e instanceof ArrayIndexOutOfBoundsException) {
		return "index";
	}
	
	return "runtime";
}

在处理异常时,如果需要转发数据,可以将返回值修改为ModelAndView,或者,在处理异常的方法参数列表中添加HttpServletRequest,但是,却不可以使用ModelMap来封装转发的数据:

@ExceptionHandler
public String handleException(Exception e,
		HttpServletRequest request) {
	System.out.println(
		"ExceptionController.handleException()");
	
	if (e instanceof NullPointerException) {
		return "null";
	} else if (e instanceof ArrayIndexOutOfBoundsException) {
		request.setAttribute("msg", e.getMessage());
		return "index";
	}
	
	return "runtime";
}

思考:是否可以在类中添加3个处理异常的方法,分别只处理NullPointerExceptionArrayIndexOutOfBoundsExceptionRuntimeException

答案:可以!允许使用多个不同的方法来处理异常!

思考:假设处理异常的方法在A控制器中,而B控制器中处理请求时出现异常,会被处理吗?

答案:不可以!通常,可以创建一个BaseController,作为当前项目的控制器类的基类,然后,把处理异常的代码编写在这个类中即可!

关于@ExceptionHandler,还可以用于限制其对应的方法处理的异常的种类!例如:

@ExceptionHandler(IndexOutOfBoundsException.class)

以上代码表示接下来的方法只处理IndexOutOfBoundsException及其子孙类异常,而其它的异常并不处理!

3.4 小结

处理异常的本质并不可以“撤消”异常,无法让已经出现的问题还原到没有问题!所以,处理异常的本质应该是尽量的补救已经出现的问题,并且,尝试通过提示信息等方式告之使用者,尽量执行正确的操作,避免后续再次出现同样的问题!

并且,对于开发者而言,应该处理每一个可能出现的异常,因为,如果没有处理,就会继续抛出,最终被Tomcat捕获,而Tomcat处理的方式是把异常的跟踪信息显示在页面上,是极为不合适的!

在SpringMVC中,处理异常有2种方式,第1种是通过SimpleMappingExceptionResolver设置异常与转发目标的对应关系,第2种是使用@ExceptionHandler为处理异常的方法进行注解,推荐使用第2种方式。

通常,会把处理异常的方法写在项目的控制器类的基类中,即写在BaseController中,在使用@ExceptionHandler时,可以明确的指定所处理的异常类型,例如:@ExceptionHandler(NullPointerException),且,在控制器类中,可以编写多个处理异常的方法!

关于处理异常的方法,应该是public方法,返回值类型与处理请求的方式相同,可以是StringModelAndView或其它允许的类型,方法的名称可以自定义,参数列表中必须包括Exception类型的参数,还允许存在HttpServletRequestHttpServletResponse,不允许使用ModelMap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值