SpringMVC 教程

SpringMVC 概述

   Spring 是目前比较流行的MVC框架,让POJO处理起来变的容易,也支持Rest的Url请求。采用松散的耦合可插拔的接口,比其它MVC接口更具有扩展性和灵活性

maven+spring+Idea 实现helloworld

下面就让我们用maven+Spring+Idea 实现一个 helloWorld的程序(至于环境的搭建可以直接到网上找个教程)

添加Maven项目

  1. 选择maven-archetype-webapp 这个项目类型

    maven创建

  2. 填写GroupId和ArtifactId后直接下一步直到创建完成

    Group

  3. Maven生成的目录如下:

MVN_Index

添加SpringMVC引用

对于MVC的使用,我们首先需要添加对SpringMVC的引用,使用Maven可以方便的实现对jar包的引用和版本的管理。

  1. 添加SpringMVC的引用

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
        <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.2.RELEASE</version>
    </dependency>
    ```



    2. 添加对jsp的页面解析 jstl的引用

    ``` xml
    <dependency>
    <groupId>Javax.servlet</groupId>
    <artifactId>Javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency>
    <dependency>
    <groupId>Javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    </dependency>
    ```

    #### 添加SpringMVC配置

    1. 添加Spring的配置文件,修改WEB-INF下面的web.config,添加如下内容

    ``` xml

    <!-- 配置DispatcherServlet -->
    <servlet>
    <servlet-name>spring-mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 配置DispatcherServlet ,配置SpringfMVC配置文件的位置和名称-->
    <!--这里可以不用通过contextConfigLocation来配置SpringMVC的配置文件,可以使用默认的配置文件的目录:/WEB-INF/<servlet-name>-servlet.xml-->
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <!--对应的Mapping-->
    <servlet-mapping>
    <servlet-name>spring-mvc</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
  2. Spring文件配置MVC,在resources文件夹下面添加对应的spring-mvc.xml,添加如下内容:

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="redirectContextRelative" value="true"></property>
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    <!--配置扫描的包-->
    <context:component-scan base-package="Controller"></context:component-scan>
    

添加Controller和views

  1. 在main文件夹下添加Java目录,并标记为SourceRoot

    SPRING_MarkAs

  2. 添加Controller包,添加一个Controller代码:

    @Controller
    public class HelloController {  
        
        @RequestMapping("/Hello")
        public String Hello(){
            return "index";
        }
        
    }
    
  3. 添加views文件夹,新建一个index.jsp页面

    <html>
    <body>
    <h2>Hello World!</h2>
    </body>
    </html>
    

整体的项目的目录结构如下:

Contact

配置Tomcat

Tomcat

运行 Hello World

Run

HelloWorld运行的过程

当我们在浏览器中发送一个Hello的请求,会被servlet-mapping所拦截,根据url的匹配格式跳转到指定的Controller,返回对应的值index值.

返回的值,会被指定的视图解析器解析为指定的物理的视图。对于 InternalResourceViewResolver 视图解析器,会做如下的的解析:

prefix+returnVal+suffix

这样的方式解析到指定的物理视图.


RequestMapping修饰方法


RequestMapping修饰方法

在上面的Demo中,我们用RequestMapping来修饰对应Controller中对应的方法,来说明当前的方法是这了响应Hello的请求。

RequestMapping的Value支持Ant通配符

在**@RequestMapping(“/Hello”)**映射中,我们让其匹配的是/Hello的url地址,RequestMapping也支持Ant通配符,具体的内容如下:

Ant 风格资源地址支持 3 种匹配符:

  • ?:匹配文件名中的一个字符
  • *:匹配文件名中的任意字符
  • 匹配多层路径

@RequestMapping 还支持 Ant 风格的 URL:

  • /user/*/createUser: 匹配
  • /user/aaa/createUser、/user/bbb/createUser 等 URL
  • /user/**/createUser: 匹配 –
  • /user/createUser、/user/aaa/bbb/createUser 等 URL
  • /user/createUser??: 匹配 –
  • /user/createUseraa、/user/createUserbb 等 URL

如果我们修改上述代码为:

  @RequestMapping("/Hello/*/123")
    public String Hello(){
        return "index";
    }

则使用:
http://localhost:8080/SpringMVC/Hello/myMvc/123
http://localhost:8080/SpringMVC/Hello/myMvc1231/123
….
都可以访问到Hello方法

RequestMapping修饰类

对于上面的Demo我们可以在HelloController上面添加RequestMapping来指定访问url的前缀的路径:

@RequestMapping("/SpringMVC")
@Controller
public class HelloController {

    @RequestMapping("/Hello")
    public String Hello(){
        return "index";
    }

}

如果Controller没有修复Request的修饰,则代表的是web的根目录。

RequestMapping的请求方式

RequestMapping可以指定请求的方式,demo如下:

@RequestMapping(value = "GetName",method = RequestMethod.GET)
public String GetName(){
    return "success";
}

@RequestMapping(value = "PostName",method = RequestMethod.POST)
public String PostName(){
    return "success";
}

修改Index的页面和添加一个success页面

<html>
<body>
<a href="/SpringMVC/GetName">GetName</a>
<br>
<a href="/SpringMVC/PostName">PostName</a>
</body>
</html>   

Post页面的请求结果 :

测试Post

RequestMapping 指定Header和Params

RequestMapping支持对参数和Header的定义,可以支持简单的表达式:

  • param1: 表示请求必须包含名为 param1 的请求参数
  • !param1: 表示请求不能包含名为 param1 的请求参数
  • param1 != value1: 表示请求包含名为 param1 的请求参数,但其值
    不能为 value1
  • {“param1=value1”, “param2”}: 请求必须包含名为 param1 和param2
    的两个请求参数,且 param1 参数的值必须为 value1
@RequestMapping(value ="TestParamsAndHeaders",method = RequestMethod.GET,params = {"userName","age!=10"},headers = {"Accept-Language:zh-CN,zh;q=0.8,en;q=0.6"})
    public String TestParamsAndHeaders(){
        return "success";
}
<a href="/SpringMVC/TestParamsAndHeaders?userName=fuwei&age=11">TestParamsAndHeaders1</a><!--可以访问 -->
<br>
<a href="/SpringMVC/TestParamsAndHeaders?userName=fuwei&age=10">TestParamsAndHeaders2</a><!--不可以访问 -->
<br>
<a href="/SpringMVC/TestParamsAndHeaders?loginName=fuwei&age=10">TestParamsAndHeaders3</a><!--不可以访问 -->

上面的方法的映射要求是:必须要有userName参数,age!=10,且只接受zh-CN的语言的请求,如果修改上面的header中的accept的语言,则都无法请求。使用params和header可以更加精确的映射请求。

PathVariable注解

通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx”) 绑定到操作方法的入参中。这个能使得SpringMVC可以支持REST风格(关于Rest)。

@RequestMapping("/GetNameById/{id}")
public String GetNameById(@PathVariable("id") Integer id){
    System.out.println(id);
    return "success";
}

在浏览器中访问:

http://localhost:8080/SpringMVC/GetNameById/123123

可以在控制台打印出:123123


SpringMVC 获得请求参数方式


使用 @RequestParam

RequestParam来映射对应的参数,它具有3个属性:

  • value : 当前参数的值

  • require: 是否必须,默认是true

  • defalutValue: 默认值

@RequestMapping("/TestRequestParam")
public String TestRequestParam(@RequestParam(value = "userId",defaultValue = 0,required = true) int uid){
    System.out.println(uid);
        return "success";
}

访问:http://localhost:8080/SpringMVC/TestRequestParam?userId=123 会在控制台打印出123

POJO 参数传递

对于表单提交来说,可能会有多字段,如果都使用@RequestParam则会比较麻烦。
针对这个问题我们可以使用POJO的方法进行传递 ,
Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。也可以使用级联属性。
如:userEx.dept.deptId、dept.address.tel 等

@RequestHeader 与@RequestCookie 注解

(其中的属性值与RequestParam 相同,不再赘述~~)


@RequestMapping("/TestRequestHeader")
public String TestRequestHeader(@RequestHeader(value = "Accept-Language") String lan){
    System.out.println(lan);
    return "success";
}

访问:http://localhost:8080/SpringMVC/TestRequestHeader 打印出指定的语言版本:

zh-CN,zh;q=0.8,en;q=0.6

@RequestMapping("/TestRequestCookie")
public String TestRequestCookie(@CookieValue(value = "JSESSIONID") String sid){
    System.out.println(sid);
    return "success";
}

访问:http://localhost:8080/SpringMVC/TestRequestCookie 打印出Cookie中的JSESSIONID。


@RequestMapping(value = "/TestPojo",method = RequestMethod.POST)
public String TestPojo(User user){
    ObjectMapper map=new ObjectMapper();
    try {
        System.out.println(map.writeValueAsString(user));
    } catch (IOException e) {
        e.printStackTrace();
    }
    return "success";
}

对应的html代码:

<form action="/SpringMVC/TestPojo" method="post">
    <input name="UserName"/><br><br>
    <input name="UserMail"/><br><br>
    <input name="Dept.DeptId"/><br><br>
    <input name="Dept.Addr.Povince"/><br><br>
    <input name="Dept.Addr.City"/><br><br>
<input type="submit" value="Submit">
</form>

填写信息打,在后台印出:

{
    "userName": "username",
    "dept": {
        "addr": {
            "povince": "shanghai",
            "city": "changning"
        },
        "deptId": 10
    },
    "userMail": "userMail"
}

Servlet原生的API参数

SpringMVC支持以下类型Servlet参数 :

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • Java.security.Principal
  • Locale
  • InputStream
  • OutputStream
  • Reader
  • Writer
@RequestMapping(value = "/TestServletApi")
public String TestServletApi(HttpServletRequest request, HttpServletResponse response){
    try {
        System.out.println("TestServletApi HttpServletRequest:"+request.getRequestURL());
        response.getWriter().write("<h1>Hello Servlet<h1/>");
        response.getWriter().close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return  "success";

}

访问:http://localhost:8080/SpringMVC/TestServletApi

控制台打印: http://localhost:8080/SpringMVC/TestServletApi

浏览器返回:Hello Servlet

helloServlet

说说.Net与Java中的字符串

Java字符串碰到的问题

在写Java程序碰到一个问题,而正是这个问题引发了我对字符串的思考,Java示例代码如下:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

public void TestStr(String success)
{

if(success=="Y"){
System.out.println("Equal");
}
else
{
System.out.println("Not Equal");
}

}

```

上面的这个函数很简单,但会随着调用的方式的不同而显示出不同的结果:

``` Java
public void CallMethod()
{
TestStr("Y");//Equal
TestStr("YY".substring(0,1));//Not Equal
}

```

对于这样的一个结果,我们可以先思考一个问题:" == " 这个运算符的作用?

1. 对于基础数据类型而言是比较值是否相同(作用与equal相同)

2. 对于引用类型,则比较地址是否一样


但如果理解上面的代码,我们还要理解Java中字符串的机制。由于字符串是比较常用的类型,为了保证性能,所以在设计字符串的时候会有一个“池”的概念。

- 字符一旦创建成功后,就不再发生变化,字符的运算也都是创建新的字符串对象

- 字符创建前,查找内存中是否已经存在相同的字符串,如果有则直接把地址给当前的对象,没有则直接创建新对象


所以对于上面的代码,因为在开始已经创建的“Y”字符串,所以后面出生现的所有的“Y”都是引用我们当前的“Y”,所以我们就可以理解为什么第一个是打印Equal,另一个是打印Not Equal.


### .Net中如何处理

而对于.Net来说,字符串的原理大致相同,如果是相同的代码,但运算的结果是与Java不一样的:

![dotnet](/img/assets/14/01.png)


我们知道在.Net string也是引用类型,但当“==”作用于两个引用类型的时候,比较则是地址,但在.Net中字符比较时,比较的却是值。这个归功于.Net对“==”的重载,[string源码](http://referencesource.microsoft.com/#mscorlib/system/string.cs,8281103e6f23cb5c){:target="_blank"}。如果想比较地址,则使用 object.ReferenceEquals()这个函数。


``` C#

public static bool operator == (String a, String b)
{
return String.Equals(a, b);
}

```

对于.Net运算符重载的这个动作,个人觉得更贴近日常的使用习惯,因为在编码的过程中,字符串中绝大多数的使用场景都是值,而不是引用。而对Java而言,保证的运算的原汁原味,少了人为的封装的干扰,使用是注意区分,习惯了反而觉得更为合理。

### 几个疑问


#### 字符串是引用类型,为什么不使用new来创建对象?

字符串是一个特殊的引用对象 ,声明就是创建了一个对象,如果使用new,则会重复的创建对象(Java中可以使用new创建,.Net中则直接不允许这样操作),浪费内存,如下:

``` Java    

String str=new String("1234");

String str1="1234";

两种的定义方式相同,但是使用new的时候,又额外分配了内存空间。

字符串是引用类型,但是传参的时候却无法修改它的值?有其它的引用类型有什么不同?

  public void CallMethod(){

    String str="abc";
    AddSuffix(str);

    System.out.println(str);//打印出abc
  }
   
  public void AddSuffix(String x){

      x=x+"123";
  }


当我们去调用这个函数的时候,发现str的值却没有发生改变。 因为在调用AddSuffix 函数时,str把自己作拷贝成一个副本传递给形参x,当对x赋值的时候,系统重新创建了一个字符对象,把引用的地址给x,此处是重新创建对象,而不是修改原来的字符串对象(字符串不可更改)。两种方式示意如下:

字符串

字符串

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×