Spring MVC框架一个实例的手动实现
文件结构:
SpringMVC05 // 应用程序名
----index.html // 欢迎文件,主目录下的文件可以被URL直接访问到
----WEB-INF // 这个目录下的文件将被保护起来不能直接被URL访问
--------web.xml // 应用的配置文件
--------classes // 放置Java类文件
--------config // 这是指定的SpringMVC配置文件的路径
--------jsp // 放置不想被URL直接访问的静态文件
--------lib // 引用的库文件
欢迎文件:
配置将在Web.xml文件中进行,我配置为 地址/SpringMVC05 将会访问到欢迎文件
<li><a href="list_book">show all books</a></li>
<li><a href="input_insert_book">insert a book</a></li>
如果超链接没有在最前面加上’/’那么将会扩展到当前URL的后面形成新的URL
如果加上了’/’将会在应用程序的根路径下扩展URL
Web.xml文件:
在SpringMVC框架下,不需要配置自己的servlet,只需要配置一个框架下已有的DispatcherServlet,他将完成许多工作,比如解析URL等,不需要再自己实现
配置DispatcherServlet的时候会需要一个配置文件
<servlet-class>是固定的,<servlet-name>自己定义,<url-pattern>表示在怎样的URL下使用DispatcherServlet,一般是表示所有URL的“/”
<servlet>
<servlet-name>springdemo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springdemo</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
DispatcherServlet在初始化的时候会去WEB-INF下找对应的 servletname-servlet.xml,这个servletname是自己定义的。
如果不想使用默认的配置文件,那么可以在<init-param>中配置值当的文件名和文件路径,如:
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>路径/xxx.xml</param-value>
</init-param>
还要再此文件中配置欢迎文件等
Config文件夹:
放置指定的配置文件,DispatcherServlet的配置文件用于配置这个servlet的行为属性等
比如去哪个包扫描由注解指定的控制器,哪个包扫描由注解指定的用于依赖注入的Service,配置视图解析器-去掉共有的前后缀
下面这个元素的配置也是必要的,缺少元素可能会导致错误
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
下面的元素是配置Spring框架的扫描包,当框架想要找控制器或用于依赖注入的类的时候会到这里配置的包下找
<context:component-scan base-package="SpringMVC.service"/>
<context:component-scan base-package="SpringMVC.controller" />
因为DispatcherServlet配置的是拦截所有URL,但是指向静态页面的URL是不需要DispatcherServlet处理的,所以要在这里配置怎样的URL不会被DispatcherServlet拦截:这里配置的是后缀为.html的URL将不会被拦截
<mvc:annotation-driven />
<mvc:resources location="/" mapping="/*.html" />
配置视图解析器:为避免每次调用视图的时候都需要完整的路径,所以在视图解析器中配置共有的前缀后缀
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
Lib库文件:
在使用多个库文件的时候,如果有jar包冲突的话,会在运行的时候产生500服务错误。手动的弊端,所以最好使用同一版本的jar文件
Classes文件夹:
文件结构:
Classes
---- SpringMVC
--------controller
------------BookController.java
--------service
------------BookService.java
------------BookServiceImpl.java
--------model
------------Book.java
含有三个包:SpringMVC.controller,SpringMVC.service,SpringMVC.model
控制器:
使用基于注解的控制器:@Controller来源于org.springframework.stereotype.Controller,标识一个类为控制器类。Spring框架通过扫描来寻找应用程序中所有基于注解的控制器类
在Spring配置文件中应用<component-scan/>元素来配置Spring去哪里扫描控制器
<context:component-scan base-package=”basePackage”>
通过注解@RequestMapping指定一个方法映射到的URI和方法。
- 可以用于标识一个控制器类的URL,表示类中的方法共有的URL前缀。如果只有一个参数,那么默认为value。
- @RequestMapping(value = “URL” method = {RequestMethod.XX})
- 修饰的方法成为请求处理方法
返回值:ModelAndView/Void/String等。String会被Spring框架解析为视图的路径。但是在String前面加上”forward:”或”redirect:”是生成一个重定向或转发的URL
参数:如果有Model参数,那么Spring MVC会在请求处理方法被调用的时候创建Model对象,用于添加在视图中要显示的属性
@RequestBody用于修饰已经被@RequestMappin修饰的方法。通常由@RequestMapping修饰的方法返回值会被解析为跳转路径,比如String、ModelAndView等,当该方法再次被@ RequestBody修饰之后,返回值将直接写入到HTTP的响应正文中。通常用于异步请求
请求参数:传统Servlet将通过HttpServletRequest.getParameter方法获取参数。Spring MVC允许直接将参数传递到方法中。在方法的参数列表中用@RequestParam标识这是一个从URL中获取的参数
路径变量:在@RequestMapping配置的URL模式的后面续加用花括号包含的参数名,这样就可以在请求URL中用“/”之间的字符串为参数赋值。Spring为尽力将路径变量转化为一个非字符串类型
PathVariable如果不写全参数会认为是debug级别的,会在运行的时候去掉,导致找不到变量Name for argument type [long] not available, and parameter name information not found in class file either.
@RequestMapping(value = "/delete_book/{id}", method = {RequestMethod.POST,RequestMethod.GET})
public String deleteBook(Model model,@PathVariable(value="id") int id){...}
依赖注入:
在控制器中需要调用数据库的服务,数据库的服务封装在Service包中,所以需要一个service类,这里通过依赖注入实现
Service包中实现了一个BookService接口,定义了会用到的基本方法。还有一个实现该接口的类BookServiceImpl
在控制器中将会使用这个接口作为对象名,而使用BookServiceImpl作为实际对象,这样在改变接口的具体实现方式的时候不需要修改控制器,降低耦合
将BookServiceImpl类用@Service注解,这样Spring框架就会直到这个类可以用于依赖注入,框架会在需要的地方通过调用这个类的无参构造函数生成对象进行注入。这里无参构造函数是必要的,否则将无法注入
在控制器中只需要声明一个引用,并将这个引用通过@Autowired注解,那么框架就会知道这里需要生成一个合适的对象赋给这个引用。而这里只需要创建一个接口类型的引用
请求处理方法:
listBook:该控制器向服务类请求数据库中所有数据,将返回的数据放到Model中发送到lit_book页面
@RequestMapping(value = "/list_book", method = {RequestMethod.POST,RequestMethod.GET})
public String listBook(Model model)
{
ArrayList<Book> bookList = bookService.get();// 向服务器类请求数据
model.addAttribute("bookList",bookList);// 请求到的数据发送到页面
return "list_book";// 调用页面
}
inputInsertBook:当点击页面上的新增数据按钮时会生成URL:”SpringMVC05/input_insert_book”,即跳转到这个请求处理方法,这个方法创建一个对象传到页面用于接收页面的表单数据,返回” input_insert_book”解析为input_insert_book.jsp页面
@RequestMapping(value = "/input_insert_book", method = {RequestMethod.POST,RequestMethod.GET})
public String inputInsertBook(Model model)
{
model.addAttribute("book",new Book());
return "input_insert_book";
}
insertBook:在提交插入表单的时候会生成URL:”SpringMVC05/input_insert_book”,即跳转到这个请求处理方法,方法中声明参数@ModelAttribute Book book用于接收页面生成的表单数据,向Model中写入插入数据库中成功或失败的信息,转发到控制器listBook,此时listBook的Model中将含有成功或失败的信息
当字符串加上forward或redirect修饰的时候,框架将不会把这个字符串解析为视图,而是重新生成一个URL。此时这个URL将会被分配到listBook
@RequestMapping(value = "/insert_book", method = {RequestMethod.POST,RequestMethod.GET})
public String insertBook(Model model,@ModelAttribute Book book)
{
// ...
return "forward:/list_book";// 转发到新的URL
}
可以看出URL是插入的URL而不是展示的,这正是转发的效果
deleteBook:当在list_book页面上点击删除按钮时,页面会在生成的URL后面加上参数id,值为对应的条目的id号,请求处理方法将根据这个值删除数据库中的数据,方法的参数列表需要声明对应的路径变量@PathVariable(value="id") int id。向Model中写入插入数据库中成功或失败的信息,转发到控制器listBook,此时listBook的Model中将含有成功或失败的信息
inputUpdateBook:当在list_book页面上点击编辑按钮时,页面会在生成的URL后面加上参数id,值为对应的条目的id号,方法的参数列表需要声明对应的路径变量@PathVariable(value="id") int id,方法会根据这个id向数据库请求旧数据并发送到页面,方法也会将id发送到页面,因为随后页面需要将id发送到更新的请求处理方法用于更新指定项。向Model中写入一个承接页面表单输入数据的book对象。跳转到input_update_book页面
@RequestMapping(value = "/input_update_book/{id}", method = {RequestMethod.POST,RequestMethod.GET})
public String inputUpdateBook(Model model,@PathVariable(value="id") int id)
{
model.addAttribute("oldBook",bookService.get(id));
model.addAttribute("id",id);
model.addAttribute("book",new Book());
return ("input_update_book");
}
updateBook:页面将id放到路径变量中,将新数据对应的对象放到方法参数的@ModelAttribute Book book中。方法向数据库更新数据。转发到litBook方法
注:我在请求处理方法中重新生成URL调用了其他的请求控制方法,这样做是不好的,层间的调用会使代码结构混乱。更好的方式是重新调用服务层获取数据,返回视图。而不是转发到另一个请求处理方法。
Model文件夹:
有一个Book类和Category类,Book类的一个数据成员是Category类,需要实现无参构造函数,框架将会使用到,否则可能会无法生成对象而产生错误
需要为数据实现get和set访问器
Service文件夹:
BookService接口和实现BookService接口的BookServiceImpl类
接口中声明将会使用到的方法签名
BookServiceImpl中实现这些方法:首先在无参构造函数中调用初始化函数,用于形成对数据库的连接。在实现的接口类中将会通过初始化生成的数据库连接进行数据库操作。这个类需要用@Service进行注解,用于依赖注入
// constructor
public BookServiceImpl()
{
init();
}
// initial function
// connect database
private void init()
{
// database
try
{
Class.forName("oracle.jdbc.driver.OracleDriver");// 加载Oracle驱动程序
String url = "jdbc:oracle:thin:@localhost:xxxx:xxxx";// localhost是本机地址,可更改为IP地址
String user = "biaoJM";
String password = "xxxxxx";
connectionOfOracle = DriverManager.getConnection(url, user, password);// 获取连接
statementOfOracle = connectionOfOracle.createStatement();
} catch (Exception e)
{
e.printStackTrace();
}
}
Jsp文件夹:
会有三个视图,对应三个文件
对于要使用特殊功能的需要在开头加上对应的项:
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
List_book:用于显示从数据库中所有的数据以及必要的信息,比如当删除一条数据后将会自动跳转到这个界面并显示删除成功的信息。并且每一条数据后面都对应有修改和删除按钮。最下面会有插入数据的按钮。因为传到视图的数据是一个数组,所以循环展示
<c:forEach items="${bookList}" var="book">
<tr>
<td>${book.id}</td>
<td>${book.category.name}</td>
<td>${book.name}</td>
<td>${book.author}</td>
<td>${book.price}</td>
<td><a href="/SpringMVC05/input_update_book/${book.id}">edit</a></td>
<td><a href="/SpringMVC05/delete_book/${book.id}">delete</a></td>
</tr>
</c:forEach>
input_insert_book:是用户想要插入一条数据的时候看到的界面,用户会在相应的输入框输入数据,有提交按钮,点击后会调用另外的方法插入数据
<form:form commandName="book" action="/SpringMVC05/insert_book" method="POST">
<tr>
<td>category id:</td>
<td><form:input id="categoryid" path="category.id"/></td>
</tr>
input_update_book:是用户想要更新一条数据的时候看到的界面,输入框会默认显示当前要修改的项的旧数据,用户修改后提交,将会调用相应的更新方法进行更新。value表示设置默认值
<tr>
<td>category id:</td>
<td><form:input id="categoryid" path="category.id" value="${oldBook.category.id}"/></td>
</tr>
页面和控制器的数据传输:
方法->页面:
请求处理方法将含有Model参数,框架在调用方法的时候会创建这个对象,用于向这个方法转到的地方发送数据。通过model的model.addAttribute(“identifier”,Object o)方法向这个对象中添加数据,如果这个方法返回到的URL是一个页面,那么在页面中就可以通过${identifier}来访问,也可以通过”.”来访问类的属性,这时框架是通过get/set访问器进行的。如果这个方法通过重定向或转发返回到的URL定向到了另一个请求处理方法,那么重定向的数据将会丢失,转发的数据将会整合到新的请求处理方法的Model中
页面->方法:
通过表单标签的数据绑定功能实现,
在页面文件头加上%@taglib uri="http://www.springframework.org/tags/form" prefix="yourPrefix"%来声明这个文件将使用Spring的表单标签进行数据绑定,并且是通过在标签的前面加上”yourPrefix:”前缀实现声明的,在form标签中的commandName属性来指定这个表单绑定到哪个对象中,在form的input子选项中通过path属性指定这个项绑定到对象的哪个属性,如果属性也是一个对象,可以通过”.”运算符指定到内部的属性。这个对象将提交到form的action指定的URL中。在调用这个页面的方法中需要向这个页面发送一个对象作为容器,form的数据将绑定到这个对象中,并提交这个对象。接收的方法将需要在参数列表中添加 @ ModelAttribute注解的对象,用于接收对象。这三个部分指定的对象名需要保持一致
编译:
javac -cp ..\lib\spring-beans-4.0.0.RELEASE.jar;..\lib\spring-context-4.0.0.RELEASE.jar;..\lib\spring-web-4.0.0.RELEASE.jar;..\lib\spring-core-4.0.0.RELEASE.jar;..\lib\spring-expression-4.0.0.RELEASE.jar;..\lib\standard.jar;..\lib\spring-aop-4.0.0.RELEASE.jar;..\lib\spring-webmvc-4.0.0.RELEASE.jar;..\lib\jstl.jar;..\lib\commons-logging-1.1.2.jar;D:\work\java\web\apache-tomcat-7.0.90\lib\servlet-api.jar;. SpringMVC\controller\BookController.java
问题:
当Spring的版本和JDK的版本有冲突的时候,也会是500,此时会在Tomcat的界面上显示异常。
Spring3.x和JDK8有兼容问题,所以要使用更高的Spring版本