SpringMVC的设计模式

单例模式

单例模式是常见的一种设计模式,确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式满足如下条件:

  1. 只能有一个实例;

  2. 它必须自行创建这个实例;

  3. 它须自行向整个系统提供这个实例。

单例模式根据创建实例的时机分为懒汉模式和饿汉模式。

懒汉模式

​ 所谓懒汉模式是指在类加载的时候不需要创建实例,采用延迟加载的方式。在运行时调用时才创建实例。使用“时间换空间”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
//用一个null值的变量来存放实例,在类加载的时候没有创建实例
private static Singleton instance = null;
//私有的构造方法
private Singleton() {

}
//调用这个方法的时候首先看看是不是第一次调用
public static Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}
}

这种方式保证了延迟加载的特性,从线程安全的角度上来说,懒汉式是不安全的,在多线程下无法正常工作。,假设,现在有线程A和线程B同时去调用getInstance方法,就可能出现线程并发的情况。解决方法就是线程同步,使用synchronized关键字解决。

1
2
3
4
5
public static synchronized Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}

饿汉模式

所谓饿汉模式是指在类加载的时候就完成了初始化操作,所以类加载的速度较慢。但是获取速度很快,使用“空间换时间”。由于饿汉模式在初始化已经自行实例化,因此不存在线程安全问题。

1
2
3
4
5
6
7
8
9
10
public class Singleton {
//用static修饰是为了在类加载的时候就创建实例
private static Singleton instance =
//私有的构造方法
private Singleton() {
}
//static修饰可以通过类直接调用这个方法
public static Singleton getInstance() {
return instance;
}

在实际应用中可能需要使用单例模式创建对象,又需要延迟加载提高性能,并且还要解决线程安全的问题。可以使用静态内部类的方式实现。

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private static Singleton instance = null;
//私有的构造方法
private Singleton() {

}
public static class SingletonHelper{
private static final Singleton INSTANCE=new Singleton();
}
//static修饰可以通过类直接调用这个方法
public static Singleton getInstance() {
instance=SingletonHelper.INSTANCE;
return instance;
}

Singleton类被加载后不一定被初始化,只有当getInstance方法被主调用时,SingletonHelper类才被加载。既满足了延迟加载的特性,又满足了线程安全。

SpringMVC-Controller的单例管理

​ SpringMVC的Contoller类是设计为单例模式的,不需要每次都创建实例。主要是基于性能的考虑。所以,一般不要在Controller中定义成员变量,否则会导致严重的线程安全以及性能问题。可能出现多次请求看到相同的数据。因为属性在内存只有一份数据。可以使用@Scope(“prototype”)将Controller变成多例的来解决,但是这种方式效率低,也违背了SpringMVC设计的初衷。通常情况下,在Contoller中只有业务类作为成员变量,但是业务类通常是接口,只有方法,没有属性,所以不存在线程安全的问题。

配置文件

在工程的类路径即resources目录下创建 SpringMVC 的配置文件 springmvc.xml。该文件名可以任意命名。推荐使用springmvc.xml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--扫描包-->
<mvc:annotation-driven/>
<!-- 静态配置文件 -->
<mvc:default-servlet-handler/>
<!-- 包含Controller的文件 -->
<context:component-scan base-package="io.peng"/>

<!--jsp视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>

请求规则

指定post提交,method属性

1
2
3
4
@RequestMapping(value = "/main",method = RequestMethod.POST)
public String one() {
return "main";
}
序号 请求方式 提交方式
1 表单请求 默认get,可以指定post
2 ajax请求 默认get,可以指定post
3 地址栏请求 get请求
4 超链接 get请求
5 src资源路劲请求 get请求

四种跳转方式

默认的跳转是请求转发,直接跳转到jsp页面展示,还可以使用框架提供的关键字redirect,进行一个重定向操作,包括重定向页面和重定向action,使用框架提供的关键字forward,进行服务器内部转发操作,包括转发页面和转发action。当使用redirect和forward关键字时,视图解析器就无效了。

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Controller
@RequestMapping("/one")
public class One {
//默认的请求转发跳页面
@RequestMapping("/requestpage")
public String jump1(){
//默认就是请求转发跳页面
return "main";
}

//请求转发跳controller
@RequestMapping("/requestController")
public String requestController(){
System.out.println("one.........");
return "forward:/two/demo.action";
}

//重定向跳页面redirect
@RequestMapping("/redirectpage")
public String redirectpage(){
//默认就是请求转发跳页面
return "redirect:/main.jsp";
}

//重定向跳controller
@RequestMapping("/redirectcontroller")
public String redirectcontroller(){
//默认就是请求转发跳页面
return "redirect:/two/demo.action";
}
}

支持的默认参数类型

这些类型只要写在方法参数中就可以使用了。
1)HttpServletRequest 对象
2)HttpServletResponse 对象
3)HttpSession 对象
4)Model/ModelMap 对象 
5)Map<String,Object>对象

注意Model,Map,ModelMap都使用的是request请求作用域,意味着只能是请求转发后,页面才可以得到值

拦截器

使用场景
1、日志记录:记录请求信息的日志
2、权限检查,如登录检查
3、性能检测:检测方法的执行时间

实现的两种方式

继承HandlerInterceptorAdapter的父类
实现HandlerInterceptor接口

而HandlerInterceptor接口中含有三个方法

  • preHandle
    该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。
  • postHandle
    该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。
  • afterCompletion
    当preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对 ModelAndView 再操作也对响应无济于事。afterCompletion 最后执行的方法,清除资源,例如在 Controller 方法中加入数据等。

以下示例重写HandlerInterceptorAdapter的preHandle方法,实现网站访问权限的过滤功能。

(1)编写拦截器功能类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SecurityInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {

User user=(User) request.getSession().getAttribute("user");
if(user==null){
String logintUrl=request.getContextPath()+"/"+"login";
String returnUrl=request.getServletPath();
response.sendRedirect(logintUrl+"?returnUrl="+returnUrl);
return false;
}
return true;
}

只有登录用户才能进入后台管理,并且记录下用户请求的当期路径,然后登录成功时自动进入之前请求的代码

自动跳转到之前请求的URL

UserController的核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

@RequestMapping(value="/login",method=RequestMethod.GET)
public String login()
{
return "login";
}
@RequestMapping(value="/login",method=RequestMethod.POST)
public String login(String username, String password, HttpSession session,String returnUrl, Model model)
{
User user=userBiz.checkLogin(username, password);
if(user!=null)
{
session.setAttribute("user",user);
if(returnUrl!=null)
{
return "redirect:"+returnUrl;
}
else
{
return "redirect:/movie/list";
}
}
else {
model.addAttribute("error","用户名或者密码错误");
return "login";
}
}

配置拦截器类

1
2
3
4
5
6
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/movie/**" />
<bean class="com.mycinema.web.interceptor.SecurityInterceptor" />
</mvc:interceptor>
</mvc:interceptors>

​根据配置,当请求路径为/movie/下任意目录(**代表目录,而*代表文件)路径时,会应用SecurityInterceptor拦截器。