先来构建一个极为简单的web应用,从controller到dao。不考虑具体实现,只是先对整体架构有一个清晰的了解。日后在分层细述每一层的细节。
我们将用到如下jar包:
引用
aopalliance-1.0.jar
commons-logging-1.1.1.jar
log4j-1.2.15.jar
spring-beans-2.5.6.jar
spring-context-2.5.6.jar
spring-context-support-2.5.6.jar
spring-core-2.5.6.jar
spring-tx-2.5.6.jar
spring-web-2.5.6.jar
spring-webmvc-2.5.6.jar
先看web.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- id="WebApp_ID"
- version="2.5">
- <display-name>spring</display-name>
-
- <context-param>
- <param-name>webAppRootKey</param-name>
- <param-value>spring.webapp.root</param-value>
- </context-param>
-
- <context-param>
- <param-name>log4jConfigLocation</param-name>
- <param-value>classpath:log4j.xml</param-value>
- </context-param>
- <context-param>
- <param-name>log4jRefreshInterval</param-name>
- <param-value>60000</param-value>
- </context-param>
-
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/applicationContext.xml</param-value>
- </context-param>
-
- <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>
- <init-param>
- <param-name>forceEncoding</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>CharacterEncodingFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <listener>
- <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
- </listener>
-
- <servlet>
- <servlet-name>spring</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/servlet.xml</param-value>
- </init-param>
- </servlet>
- <servlet-mapping>
- <servlet-name>spring</servlet-name>
- <url-pattern>*.do</url-pattern>
- </servlet-mapping>
- <welcome-file-list>
- <welcome-file>index.html</welcome-file>
- <welcome-file>index.htm</welcome-file>
- <welcome-file>index.jsp</welcome-file>
- <welcome-file>default.html</welcome-file>
- <welcome-file>default.htm</welcome-file>
- <welcome-file>default.jsp</welcome-file>
- </welcome-file-list>
- </web-app>
有不少人问我,这段代码是什么:
-
- <context-param>
- <param-name>webAppRootKey</param-name>
- <param-value>spring.webapp.root</param-value>
- </context-param>
这是当前应用的路径变量,也就是说你可以在其他代码中使用${spring.webapp.root}指代当前应用路径。我经常用它来设置log的输出目录。
为什么要设置参数log4jConfigLocation?
-
- <context-param>
- <param-name>log4jConfigLocation</param-name>
- <param-value>classpath:log4j.xml</param-value>
- </context-param>
- <context-param>
- <param-name>log4jRefreshInterval</param-name>
- <param-value>60000</param-value>
- </context-param>
这是一种基本配置,spring中很多代码使用了不同的日志接口,既有log4j也有commons-logging,这里只是强制转换为log4j!并且,log4j的配置文件只能放在classpath根路径。同时,需要通过commons-logging配置将日志控制权转交给log4j。同时commons-logging.properties必须放置在classpath根路径。
commons-logging内容:
- org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
最后,记得配置log4j的监听器:
- <listener>
- <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
- </listener>
接下来,我们需要配置两套配置文件,applicationContext.xml和servlet.xml。
applicationContext.xml用于对应用层面做整体控制。按照分层思想,统领service层和dao层。servlet.xml则单纯控制controller层。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- 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/context http://www.springframework.org/schema/context/spring-context.xsd">
- <import
- resource="service.xml" />
- <import
- resource="dao.xml" />
- </beans>
applicationContext.xml什么都不干,它只管涉及到整体需要的配置,并且集中管理。
这里引入了两个配置文件service.xml和dao.xml分别用于业务、数据处理。
service.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- 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/context http://www.springframework.org/schema/context/spring-context.xsd">
- <context:component-scan
- base-package="org.zlex.spring.service" />
- </beans>
注意,这里通过<context:component-scan />标签指定了业务层的基础包路径——“org.zlex.spring.service”。也就是说,业务层相关实现均在这一层。这是有必要的分层之一。
dao.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
- <context:component-scan
- base-package="org.zlex.spring.dao" />
- </beans>
dao层如法炮制,包路径是"org.zlex.spring.dao"。从这个角度看,注解还是很方便的!
最后,我们看看servlet.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- 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/context http://www.springframework.org/schema/context/spring-context.xsd">
- <context:component-scan
- base-package="org.zlex.spring.controller" />
- <bean
- id="urlMapping"
- class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
- <bean
- class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
- </beans>
包路径配置就不细说了,都是一个概念。最重要的时候后面两个配置,这将使得注解生效!
“org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping”是默认实现,可以不写,Spring容器默认会默认使用该类。
“org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter”直接关系到多动作控制器配置是否可用!
简单看一下代码结构,如图:
Account类是来存储账户信息,属于域对象,极为简单,代码如下所示:
Account.java
-
-
-
- package org.zlex.spring.domain;
-
- import java.io.Serializable;
-
-
-
-
-
-
-
- public class Account implements Serializable {
-
-
-
-
- private static final long serialVersionUID = -533698031946372178L;
-
- private String username;
- private String password;
-
-
-
-
-
- public Account(String username, String password) {
- this.username = username;
- this.password = password;
- }
-
-
-
-
- public String getUsername() {
- return username;
- }
-
-
-
-
- public void setUsername(String username) {
- this.username = username;
- }
-
-
-
-
- public String getPassword() {
- return password;
- }
-
-
-
-
- public void setPassword(String password) {
- this.password = password;
- }
-
-
- }
通常,在构建域对象时,需要考虑该对象可能需要进行网络传输,本地缓存,因此建议实现序列化接口Serializable
我们再来看看控制器,这就稍微复杂了一点代码如下所示:
AccountController .java
-
-
-
- package org.zlex.spring.controller;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.ServletRequestUtils;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.zlex.spring.service.AccountService;
-
-
-
-
-
-
-
- @Controller
- @RequestMapping("/account.do")
- public class AccountController {
-
- @Autowired
- private AccountService accountService;
-
- @RequestMapping(method = RequestMethod.GET)
- public void hello(HttpServletRequest request, HttpServletResponse response)
- throws Exception {
-
- String username = ServletRequestUtils.getRequiredStringParameter(
- request, "username");
- String password = ServletRequestUtils.getRequiredStringParameter(
- request, "password");
- System.out.println(accountService.verify(username, password));
- }
- }
分段详述:
- @Controller
- @RequestMapping("/account.do")
这两行注解,
@Controller
是告诉Spring容器,这是一个控制器类,
@RequestMapping("/account.do")
是来定义该控制器对应的请求路径(/account.do)
- @Autowired
- private AccountService accountService;
这是用来自动织入业务层实现AccountService,有了这个注解,我们就可以不用写setAccountService()方法了!
同时,JSR-250标准注解,推荐使用
@Resource
来代替Spring专有的@Autowired注解。
引用
Spring 不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource、@PostConstruct以及@PreDestroy。
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了。@Resource有两个属性是比较重要的,分别是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
@Resource装配顺序
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配(见2);如果没有匹配,则回退为一个原始类型(UserDao)进行匹配,如果匹配则自动装配;
1.6. @PostConstruct(JSR-250)
在方法上加上注解@PostConstruct,这个方法就会在Bean初始化之后被Spring容器执行(注:Bean初始化包括,实例化Bean,并装配Bean的属性(依赖注入))。
这有点像ORM最终被JPA一统天下的意思!
大家知道就可以了,具体使用何种标准由项目说了算!
最后,来看看核心方法:
- @RequestMapping(method = RequestMethod.GET)
- public void hello(HttpServletRequest request, HttpServletResponse response)
- throws Exception {
-
- String username = ServletRequestUtils.getRequiredStringParameter(
- request, "username");
- String password = ServletRequestUtils.getRequiredStringParameter(
- request, "password");
- System.out.println(accountService.verify(username, password));
- }
注解@RequestMapping(method = RequestMethod.GET)指定了访问方法类型。
注意,如果没有用这个注解标识方法,Spring容器将不知道那个方法可以用于处理get请求!
对于方法名,我们可以随意定!方法中的参数,类似于“HttpServletRequest request, HttpServletResponse response”,只要你需要方法可以是有参也可以是无参!
解析来看Service层,分为接口和实现:
AccountService.java
-
-
-
- package org.zlex.spring.service;
-
-
-
-
-
-
-
- public interface AccountService {
-
-
-
-
-
-
-
-
- boolean verify(String username, String password);
-
- }
接口不需要任何Spring注解相关的东西,它就是一个简单的接口!
重要的部分在于实现层,如下所示:
AccountServiceImpl.java
-
-
-
- package org.zlex.spring.service.impl;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import org.zlex.spring.dao.AccountDao;
- import org.zlex.spring.domain.Account;
- import org.zlex.spring.service.AccountService;
-
-
-
-
-
-
-
- @Service
- @Transactional
- public class AccountServiceImpl implements AccountService {
-
- @Autowired
- private AccountDao accountDao;
-
-
-
-
-
-
-
- @Override
- public boolean verify(String username, String password) {
-
- Account account = accountDao.read(username);
-
- if (password.equals(account.getPassword())) {
- return true;
- } else {
- return false;
- }
- }
-
- }
注意以下内容:
注解@Service用于标识这是一个Service层实现,@Transactional用于控制事务,将事务定位在业务层,这是非常务实的做法!
接下来,我们来看持久层:AccountDao和AccountDaoImpl类
AccountDao.java
-
-
-
- package org.zlex.spring.dao;
-
- import org.zlex.spring.domain.Account;
-
-
-
-
-
-
-
- public interface AccountDao {
-
-
-
-
-
-
-
- Account read(String username);
-
- }
这个接口就是简单的数据提取,无需任何Spring注解有关的东西!
再看其实现类:
AccountDaoImpl.java
-
-
-
- package org.zlex.spring.dao.impl;
-
- import org.springframework.stereotype.Repository;
- import org.zlex.spring.dao.AccountDao;
- import org.zlex.spring.domain.Account;
-
-
-
-
-
-
-
- @Repository
- public class AccountDaoImpl implements AccountDao {
-
-
-
-
- @Override
- public Account read(String username) {
-
- return new Account(username,"wolf");
- }
-
- }
这里只需要注意注解:
意为持久层,Dao实现这层我没有过于细致的介绍通过注解调用ORM或是JDBC来完成实现,这些内容后续细述!
这里我们没有提到注解
@Component
,共有4种“组件”式注解:
引用
@Component:可装载组件
@Repository:持久层组件
@Service:服务层组件
@Controller:控制层组件
这样spring容器就完成了控制层、业务层和持久层的构建。
启动应用,访问 http://localhost:8080/spring/account.do?username=snow&password=wolf
观察控制台,如果得到包含“true”的输出,本次构建就成功了!
代码见附件!
顺便说一句:在Spring之前的XML配置中,如果你想在一个类中获得文件可以通过在xml配置这个类的某个属性。在注解的方式(Spring3.0)中,你可以使用
@Value
来指定这个文件。
例如,我们想要在一个类中获得一个文件,可以这样写:
- @Value("/WEB-INF/database.properties")
- private File databaseConfig;
如果这个properties文件已经正常在容器中加载,可以直接这样写:
- @Value("${jdbc.url}")
- private String url;
获得这个url参数!
容器中加载这个Properties文件:
- <util:properties id="jdbc" location="/WEB-INF/database.properties"/>
这样,我们就能通过注解
@Value
获得
/WEB-INF/database.properties
这个文件!
如果我们想要获得注入在xml中的某个类,例如dataSource(<bean id ="dataSource">)可以在注解的类中这么写:
- @Resource(name = "dataSource")
- private BasicDataSource dataSource;
如果只有这么一个类使用该配置文件:
- @ImportResource("/WEB-INF/database.properties")
- public class AccountDaoImpl extends AccountDao {
就这么简单!
对Spring注解有了一个整体认识,至少完成了一个简单的web应用搭建。当然,还不完善,这仅仅只是个开始!
今天看了Spring 3.0的注解,我感觉自己被颠覆了。多年前,为了减少代码依赖我们用配置文件进行模块间耦合,降低模块之间的黏度。现如今,所有可配置的内容都塞进了代码中,我只能说:这多少有点顾此失彼,有点倒退的意思!使用注解的好处是:代码通读性增强。这既是优势也是劣势!如果我要改一段配置,就要打开代码逐行扫描;如果恰巧这是别人封装的jar包,那我只好反编译;如果碰巧遇上这个jar包经过了混淆,那我只好求助于AOP了。 为了这么一个配置,我的代码观几乎将要被颠覆!
言归正传,研究一下注解下的控制层。
我习惯于使用JSTL展示页面,因此需要在原lib基础上增加jstl.jar和standard.jar,详细lib依赖如下:
引用
aopalliance-1.0.jar
commons-logging-1.1.1.jar
log4j-1.2.15.jar
spring-beans-2.5.6.jar
spring-context-2.5.6.jar
spring-context-support-2.5.6.jar
spring-core-2.5.6.jar
spring-tx-2.5.6.jar
spring-web-2.5.6.jar
spring-webmvc-2.5.6.jar
standard.jar
jstl.jar
上一篇文中,我们定义了控制器AccountController:
AccountController.java
-
-
-
- package org.zlex.spring.controller;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.ServletRequestUtils;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.zlex.spring.service.AccountService;
-
-
-
-
-
-
-
- @Controller
- @RequestMapping("/account.do")
- public class AccountController {
-
- @Autowired
- private AccountService accountService;
-
- @RequestMapping(method = RequestMethod.GET)
- public void hello(HttpServletRequest request, HttpServletResponse response)
- throws Exception {
-
- String username = ServletRequestUtils.getRequiredStringParameter(
- request, "username");
- String password = ServletRequestUtils.getRequiredStringParameter(
- request, "password");
- System.out.println(accountService.verify(username, password));
- }
- }
先说注解
@RequestMapping
这里使用注解
@RequestMapping(method = RequestMethod.GET)
指定这个方法为get请求时调用。同样,我们可以使用注解
@RequestMapping(method = RequestMethod.POST)
指定该方法接受post请求。
- @Controller
- @RequestMapping("/account.do")
- public class AccountController {
-
- @RequestMapping(method = RequestMethod.GET)
- public void get() {
- }
-
- @RequestMapping(method = RequestMethod.POST)
- public void post() {
- }
- }
这与我们久别的Servlet很相像,类似于doGet()和doPost()方法!
我们也可以将其改造为多动作控制器,如下代码所示:
- @Controller
- @RequestMapping("/account.do")
- public class AccountController {
-
- @RequestMapping(params = "method=login")
- public void login() {
- }
-
- @RequestMapping(params = "method=logout")
- public void logout() {
- }
这样,我们可以通过参数“method”指定不同的参数值从而通过请求("/account.do?method=login"和"/account.do?method=logout")调用不同的方法!
注意:使用多动作控制器必须在配置文件中加入注解支持!
- <bean
- class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
当然,我们也可以将注解
@RequestMapping
指定到某一个方法上,如:
- @Controller
- public class AccountController {
-
- @RequestMapping("/a.do")
- public void a() {}
-
- @RequestMapping("/b.do")
- public void b() {}
- }
这样,请求“a.do”和“b.do”将对应不同的方法a() 和b()。这使得一个控制器可以同时承载多个请求!
@RequestMapping("/account.do")
是
@RequestMapping(value="/account.do")
的简写!
再说输入参数!
这里的方法名可以随意定义,但是参数和返回值却又要求!
为什么?直接看源代码,我们就能找到答案!
AnnotationMethodHandlerAdapter.java部分源代码——有关参数部分:
- @Override
- protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest)
- throws Exception {
-
- HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
- HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();
-
- if (ServletRequest.class.isAssignableFrom(parameterType)) {
- return request;
- }
- else if (ServletResponse.class.isAssignableFrom(parameterType)) {
- this.responseArgumentUsed = true;
- return response;
- }
- else if (HttpSession.class.isAssignableFrom(parameterType)) {
- return request.getSession();
- }
- else if (Principal.class.isAssignableFrom(parameterType)) {
- return request.getUserPrincipal();
- }
- else if (Locale.class.equals(parameterType)) {
- return RequestContextUtils.getLocale(request);
- }
- else if (InputStream.class.isAssignableFrom(parameterType)) {
- return request.getInputStream();
- }
- else if (Reader.class.isAssignableFrom(parameterType)) {
- return request.getReader();
- }
- else if (OutputStream.class.isAssignableFrom(parameterType)) {
- this.responseArgumentUsed = true;
- return response.getOutputStream();
- }
- else if (Writer.class.isAssignableFrom(parameterType)) {
- this.responseArgumentUsed = true;
- return response.getWriter();
- }
- return super.resolveStandardArgument(parameterType, webRequest);
- }
也就是说,如果我们想要在自定义的方法中获得一些个“标准”输入参数,参数类型必须包含在以下类型中:
引用
ServletRequest
ServletResponse
HttpSession
Principal
Locale
InputStream
OutputStream
Reader
Writer
当然,上述接口其实都是对于HttpServletRequest和HttpServletResponse的扩展。
此外,我们还可以定义自己的参数。
注意:自定义参数必须是实现类,绝非接口!Spring容器将帮你完成对象初始化工作!
比如说上文中,我们需要参数username和password。我们可以这么写:
- @RequestMapping(method = RequestMethod.GET)
- public void hello(String username,String password) {
- System.out.println(accountService.verify(username, password));
- }
如果参数名不能与这里的变量名保持一致,那么我们可以使用注解
@RequestParam
进行强制绑定,代码如下所示:
- @RequestMapping(method = RequestMethod.GET)
- public void hello(@RequestParam("username") String u,
- @RequestParam("password") String p) {
- System.out.println(accountService.verify(u, p));
- }
这比起我们之前写的代码有所简洁:
- @RequestMapping(method = RequestMethod.GET)
- public void hello(HttpServletRequest request, HttpServletResponse response)
- throws Exception {
-
- String username = ServletRequestUtils.getRequiredStringParameter(
- request, "username");
- String password = ServletRequestUtils.getRequiredStringParameter(
- request, "password");
- System.out.println(accountService.verify(username, password));
- }
ServletRequestUtils类的工作已经由Spring底层实现了,我们只需要把参数名定义一致即可,其内部取参无需关心!
除了传入参数,我们还可以定义即将传出的参数,如加入ModelMap参数:
- @SuppressWarnings("unchecked")
- @RequestMapping(method = RequestMethod.GET)
- public Map hello(String username, String password, ModelMap model) {
-
- System.out.println(accountService.verify(username, password));
-
- model.put("msg", username);
-
- return model;
- }
这时,我们没有定义页面名称,Spring容器将根据请求名指定同名view,即如果是jap页面,则account.do->account.jsp!
不得不承认,这样写起来的确减少了代码量!
接着说输出参数!
通过ModelMap,我们可以绑定输出到的页面的参数,但最终我们将要返回到何种页面呢?再次查看AnnotationMethodHandlerAdapter源代码!
AnnotationMethodHandlerAdapter.java部分源代码——有关返回值部分:
- @SuppressWarnings("unchecked")
- public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue,
- ExtendedModelMap implicitModel, ServletWebRequest webRequest) {
-
- if (returnValue instanceof ModelAndView) {
- ModelAndView mav = (ModelAndView) returnValue;
- mav.getModelMap().mergeAttributes(implicitModel);
- return mav;
- }
- else if (returnValue instanceof Model) {
- return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
- }
- else if (returnValue instanceof Map) {
- return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue);
- }
- else if (returnValue instanceof View) {
- return new ModelAndView((View) returnValue).addAllObjects(implicitModel);
- }
- else if (returnValue instanceof String) {
- return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
- }
- else if (returnValue == null) {
-
- if (this.responseArgumentUsed || webRequest.isNotModified()) {
- return null;
- }
- else {
-
- return new ModelAndView().addAllObjects(implicitModel);
- }
- }
- else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
-
- ModelAttribute attr = AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class);
- String attrName = (attr != null ? attr.value() : "");
- ModelAndView mav = new ModelAndView().addAllObjects(implicitModel);
- if ("".equals(attrName)) {
- Class resolvedType = GenericTypeResolver.resolveReturnType(handlerMethod, handlerType);
- attrName = Conventions.getVariableNameForReturnType(handlerMethod, resolvedType, returnValue);
- }
- return mav.addObject(attrName, returnValue);
- }
- else {
- throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
- }
- }
返回值的定义十分庞大,或者说可怕的if-else多少有点让我觉得厌恶!
我们可以定义以下类型的返回值:
引用
ModelAndView
Model
View
Map
String
null
ModelAndView、Model和View都是Spring之前版本所特有的元素,Map对应于传入参数ModelMap,String定义页面名称,null即对应void类型方法!
最常用的实现方式如下:
- @SuppressWarnings("unchecked")
- @RequestMapping(method = RequestMethod.GET)
- public String hello(String username, String password, ModelMap model) {
-
- System.out.println(accountService.verify(username, password));
-
- model.put("msg", username);
-
- return "account";
- }
当然,对于我来说在返回值中写入这么一个字符串多少有点不能接受,于是我还是乐于使用输入参数ModelMap+输出参数Map的方式。
给出一个完整的AccountController实现:
AccountController.java
-
-
-
- package org.zlex.spring.controller;
-
- import java.util.Map;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.ModelMap;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.zlex.spring.service.AccountService;
-
-
-
-
-
-
-
- @Controller
- @RequestMapping("/account.do")
- public class AccountController {
-
- @Autowired
- private AccountService accountService;
-
- @SuppressWarnings("unchecked")
- @RequestMapping(method = RequestMethod.GET)
- public Map hello(String username, String password, ModelMap model) {
-
- System.out.println(accountService.verify(username, password));
-
- model.put("msg", username);
- return model;
- }
- }
最后说注解
@Session
如果想将某个ModelMap中的参数指定到Session中,可以使用
@Session
注解,将其绑定为Session熟悉,代码如下所示:
- @Controller
- @RequestMapping("/account.do")
- @SessionAttributes("msg")
- public class AccountController {
-
- @Autowired
- private AccountService accountService;
-
- @SuppressWarnings("unchecked")
- @RequestMapping(method = RequestMethod.GET)
- public Map hello(String username, String password, ModelMap model) {
-
- System.out.println(accountService.verify(username, password));
-
- model.put("msg", username);
- return model;
- }
-
- }
当然,我们还需要配置一下对应的视图解析器,给出完整配置:
servelt.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <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: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/context http://www.springframework.org/schema/context/spring-context.xsd">
- <context:component-scan
- base-package="org.zlex.spring.controller" />
- <bean
- id="urlMapping"
- class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
- <bean
- class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
- <bean
- id="jstlViewResolver"
- class="org.springframework.web.servlet.view.InternalResourceViewResolver"
- p:viewClass="org.springframework.web.servlet.view.JstlView"
- p:prefix="/WEB-INF/page/"
- p:suffix=".jsp" />
- </beans>
这里使用了JstlView作为视图解析器。同时,指定前缀路径为"/WEB-INF/page/",后缀路径为".jsp"。也就是说,Spring容器将会在这个路径中寻找匹配的jsp文件!
注意加入
xmlns:p="http://www.springframework.org/schema/p"
命名空间!
再给出页面内容:
taglib.jsp
- <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
- <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
- <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%>
- <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml"%>
- <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
- <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
- <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
account.jap
- <%@ page language="java" contentType="text/html; charset=UTF-8"
- pageEncoding="UTF-8"%>
- <%@ include file="/WEB-INF/page/taglib.jsp"%>
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Account</title>
- </head>
- <body>
- <c:out value="${msg}"></c:out>
- </body>
- </html>
目录结构如图所示:
启动应用,最后将得到一个带有内容的页面,如图:
代码见附件!
如果要说表单,最简单的就是用户登录页面了!估计大多数做B/S出身的兄弟可能写的第一个表单就是登录表单了! 今天,我也不例外,做一个登录验证实现!
首先,改造一下账户类Account,增加一个id字段:
Account.java
-
-
-
- package org.zlex.spring.domain;
-
- import java.io.Serializable;
-
-
-
-
-
-
-
-
- public class Account implements Serializable {
-
-
-
-
- private static final long serialVersionUID = -533698031946372178L;
-
-
-
-
- private int id;
-
-
-
- private String username;
-
-
-
- private String password;
-
-
-
- public Account() {
- }
-
-
-
-
- public Account(int id) {
- this.id = id;
- }
-
-
-
- }
接下来,为了协调逻辑处理,我们改造接口AccountService及其实现类AccountServiceImpl:
AccountService.java
-
-
-
- package org.zlex.spring.service;
-
- import org.springframework.transaction.annotation.Transactional;
- import org.zlex.spring.domain.Account;
-
-
-
-
-
-
-
-
- @Transactional
- public interface AccountService {
-
-
-
-
-
-
-
-
- Account read(String username, String password);
-
-
-
-
-
-
-
- Account read(int id);
- }
我们暂时抛开AccountDao该做的事情,在AccountServiceImpl中完成数据提取:
AccountServiceImpl.java
-
-
-
- package org.zlex.spring.service.impl;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.zlex.spring.dao.AccountDao;
- import org.zlex.spring.domain.Account;
- import org.zlex.spring.service.AccountService;
-
-
-
-
-
-
-
-
- @Service
- public class AccountServiceImpl implements AccountService {
-
- @Autowired
- private AccountDao accountDao;
-
- @Override
- public Account read(String username, String password) {
- Account account = null;
- if (username.equals("snowolf") && password.equals("zlex")) {
- account = new Account();
- account.setId(1);
- account.setUsername(username);
- account.setPassword(password);
- }
- return account;
- }
-
- @Override
- public Account read(int id) {
- Account account = new Account();
- account.setId(1);
- account.setUsername("snowolf");
- account.setPassword("zlex");
- return account;
- }
- }
先来一个账户信息的展示,构建一个控制器ProfileController:
ProfileController.java
-
-
-
- package org.zlex.spring.controller;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.ModelMap;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.zlex.spring.domain.Account;
- import org.zlex.spring.service.AccountService;
-
-
-
-
-
-
-
-
- @Controller
- @RequestMapping(value = "/profile.do")
- public class ProfileController {
- @Autowired
- private AccountService accountService;
-
-
-
-
-
-
-
-
- @RequestMapping(method = RequestMethod.GET)
- public String profile(@RequestParam("id") int id, ModelMap model) {
- Account account = accountService.read(id);
- model.addAttribute("account", account);
-
-
- return "account/profile";
- }
- }
@RequestMapping(value = "/profile.do")
为该控制器绑定url(/profile.do)
@RequestMapping(method = RequestMethod.GET)
指定为GET请求
model.addAttribute("account", account);
绑定账户
return "account/profile";
跳转到“/WEB-INF/page/account/porfile.jsp”页面
对应构建这个页面:
porfile.jsp
- <fieldset><legend>用户信息</legend>
- <ul>
- <li><label>用户名:</label><c:out value="${account.username}" /></li>
- </ul>
- </fieldset>
账户信息已经绑定在response的属性上。自然,使用<c:out />标签就可以获得账户信息内容。
访问地址 http://localhost:8080/spring/profile.do?id=1
,结果如图所示:
接着构建一个登录控制器LoginController
LoginController.java
-
-
-
- package org.zlex.spring.controller;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.ModelMap;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.zlex.spring.domain.Account;
- import org.zlex.spring.service.AccountService;
-
-
-
-
-
-
-
-
- @Controller
- @RequestMapping(value = "/login.do")
- public class LoginController {
-
- @Autowired
- private AccountService accountService;
-
-
-
-
-
-
-
- @RequestMapping(method = RequestMethod.GET)
- public String initForm(ModelMap model) {
- Account account = new Account();
- model.addAttribute("account", account);
-
- return "account/login";
- }
-
-
-
-
-
-
-
- @RequestMapping(method = RequestMethod.POST)
- public String login(@ModelAttribute("account") Account account) {
- Account acc = accountService.read(account.getUsername(), account
- .getPassword());
- if (acc != null) {
- return "redirect:profile.do?id=" + acc.getId();
- } else {
- return "redirect:login.do";
- }
- }
- }
分段详述,先说初始化表单:
-
-
-
-
-
-
- @RequestMapping(method = RequestMethod.GET)
- public String initForm(ModelMap model) {
- Account account = new Account();
- model.addAttribute("account", account);
-
- return "account/login";
- }
@RequestMapping(method = RequestMethod.GET)
指定了GET请求方式,这与POST表单提交相对应!
model.addAttribute("account", account);
绑定账户对象,也就是这个登录表单对象
return "account/login";
指向登录页面
再看登录方法:
-
-
-
-
-
-
- @RequestMapping(method = RequestMethod.POST)
- public String login(@ModelAttribute("account") Account account) {
- Account acc = accountService.read(account.getUsername(), account
- .getPassword());
- if (acc != null) {
- return "redirect:profile.do?id=" + acc.getId();
- } else {
- return "redirect:login.do";
- }
- }
@RequestMapping(method = RequestMethod.POST)
绑定POST表单提交请求
@ModelAttribute("account") Account account
绑定表单对象。
最后,再来看看页面:
login.jsp
- <fieldset><legend>登录</legend><form:form commandName="account">
- <form:hidden path="id" />
- <ul>
- <li><form:label path="username">用户名:</form:label><form:input
- path="username" /></li>
- <li><form:label path="password">密码:</form:label><form:password
- path="password" /></li>
- <li>
- <button type="submit">登录</button>
- <button type="reset">重置</button>
- </li>
- </ul>
- </form:form></fieldset>
注意,
<form:form commandName="account">
必须指明
commandName
,且与表单初始化、提交方法中的表单对象名称保持一致!
页面目录结构如下图所示:
在页面中,我加入了一部分css效果,这部分代码我就不在这里唠叨了,大家可以看源码!
登录试试,如图:
用户名:snwolf 密码:zlex
如果登录成功,我们就会跳转到之前的账户信息页面!
注解的确减少了代码的开发量,当然,这对于我们理解程序是一种挑战!如果你不知道原有的SpringMVC的流程,很难一开始就能摆弄清楚这些内容!
完整代码见附件!
搞搞持久层。不搞太复杂的东西,Spring注解对于持久层的改造并不难懂! 我们用最直接的JdbcTemplate诠释Spring注解持久层部分,关于业务层和事务控制,稍后详述! 某位兄弟不要着急,咱要一步一步来!
这里将用到以下几个包:
引用
aopalliance-1.0.jar
commons-collections.jar
commons-dbcp.jar
commons-logging-1.1.1.jar
commons-pool.jar
jstl.jar
log4j-1.2.15.jar
mysql-connector-java-5.1.6-bin.jar
spring-beans-2.5.6.jar
spring-context-2.5.6.jar
spring-context-support-2.5.6.jar
spring-core-2.5.6.jar
spring-jdbc-2.5.6.jar
spring-tx-2.5.6.jar
spring-web-2.5.6.jar
spring-webmvc-2.5.6.jar
standard.jar
主要增加了commons-collections.jar、commons-dbcp.jar、commons-pool.jar、mysql-connector-java-5.1.6-bin.jar和spring-jdbc-2.5.6.jar
先弄个数据库,这里使用MySQL,我的最爱!
可惜前途未卜!
建库:
- CREATE DATABASE `spring` /*!40100 DEFAULT CHARACTER SET utf8 */;
建表:
- DROP TABLE IF EXISTS `spring`.`account`;
- CREATE TABLE `spring`.`account` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `username` varchar(45) NOT NULL,
- `password` varchar(45) NOT NULL,
- `birthday` datetime NOT NULL,
- `email` varchar(45) NOT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
插入默认数据:
- INSERT INTO `spring`.`account`(
- `username`,
- `password`,
- `birthday`,
- `email`)
- VALUES(
- 'snowolf',
- 'zlex',
- '2010-01-01',
- '[email protected]');
给出一个数据库查询的结果:
很不巧,为了能让数据查询更有意义,我又要改动Account类:
Account.java
- public class Account implements Serializable {
-
-
-
-
- private int id;
-
-
-
-
- private String username;
-
-
-
-
- private String password;
-
-
-
-
- private Date birthday;
-
-
-
-
- private String email;
-
-
- }
这样,域对象与数据库表将完成一一对应绑定关系。
再建立一个用于构建数据源配置的文件database.properties
database.properties:
- dataSource.driverClassName=com.mysql.jdbc.Driver
- dataSource.url=jdbc:mysql://localhost:3306/spring
- dataSource.username=root
- dataSource.password=admin
- dataSource.maxActive=200
- dataSource.maxIdle=50
- dataSource.maxWait=10000
该文件位于/WEB-INF/目录下。
接下来,我们需要把它引入spring容器,修改applicationContext.xml:
applicationContext.xml
- <context:property-placeholder
- location="/WEB-INF/database.properties" />
如果需要引入多个properties文件,可以用逗号分隔。
这时,我们已经引入了数据源配置,我们可以通过修改dao.xml构建基于DBCP的数据源:
dao.xml中dataSource配置
- <bean
- id="dataSource"
- class="org.apache.commons.dbcp.BasicDataSource"
- destroy-method="close"
- lazy-init="false"
- p:driverClassName="${dataSource.driverClassName}"
- p:url="${dataSource.url}"
- p:username="${dataSource.username}"
- p:password="${dataSource.password}"
- p:maxActive="${dataSource.maxActive}"
- p:maxIdle="${dataSource.maxIdle}"
- p:maxWait="${dataSource.maxWait}" />
上述配置稀松平常,没有什么好阐述的内容,这与一般spring配置无异样。
需要注意的是这个jdbcTemplate配置!
dao.xml中jdbcTemplate配置
- <bean
- class="org.springframework.jdbc.core.JdbcTemplate"
- p:dataSource-ref="dataSource" />
这个配置很关键,如果你要使用其他的ORM框架,同样需要配置这样的模板类,在Dao实现中无需继承JdbcDaoSupport类。
不需要明确JdbcTemplate的id(id="jdbcTemplate")吗?不再需要了!
AccountDao.java
- public interface AccountDao {
-
-
-
-
-
-
-
- Account read(String username);
-
-
-
-
-
-
-
- Account read(int id);
- }
AccountDaoImpl.java
-
-
-
- package org.zlex.spring.dao.impl;
-
- import java.sql.ResultSet;
- import java.sql.SQLException;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.jdbc.core.RowMapper;
- import org.springframework.stereotype.Repository;
- import org.zlex.spring.dao.AccountDao;
- import org.zlex.spring.domain.Account;
-
-
-
-
-
-
-
-
- @Repository
- public class AccountDaoImpl implements AccountDao {
-
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- @Override
- public Account read(String username) {
- String sql = "SELECT * From account WHERE username = ?";
- return (Account) jdbcTemplate.queryForObject(sql,
- new Object[] { username }, accountRowMap);
- }
-
- @Override
- public Account read(int id) {
- String sql = "SELECT * From account WHERE id = ?";
- return (Account) jdbcTemplate.queryForObject(sql, new Object[] { id },
- accountRowMap);
- }
-
- protected RowMapper accountRowMap = new RowMapper() {
- @Override
- public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
- Account account = new Account();
- account.setId(rs.getInt("id"));
- account.setUsername(rs.getString("username"));
- account.setPassword(rs.getString("password"));
- account.setBirthday(rs.getDate("birthday"));
- account.setEmail(rs.getString("email"));
- return account;
- }
- };
- }
分段详述:
注解
@Repository
明确这个类是用于持久层的实现类,注意这样的注解不能用于接口,仅适用于实现类!
同时,不再需要继承JdbcDaoSupport类,其而代之的是直接注入JdbcTemplate类!
再看声明JdbcTemplate类:
- @Autowired
- private JdbcTemplate jdbcTemplate;
需要说明一下,这里的JdbcTemplate对象jdbcTemplate名称可以自定,没有任何限制!
这里使用 RowMapper 定义了一个用于绑定Account域对象的内部映射类:
RowMapper
- protected RowMapper accountRowMap = new RowMapper() {
- @Override
- public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
- Account account = new Account();
- account.setId(rs.getInt("id"));
- account.setUsername(rs.getString("username"));
- account.setPassword(rs.getString("password"));
- account.setBirthday(rs.getDate("birthday"));
- account.setEmail(rs.getString("email"));
- return account;
- }
- };
语句级的内容,十分简单,如下所示:
- @Override
- public Account read(String username) {
- String sql = "SELECT * From account WHERE username = ?";
- return (Account) jdbcTemplate.queryForObject(sql,
- new Object[] { username }, accountRowMap);
- }
-
- @Override
- public Account read(int id) {
- String sql = "SELECT * From account WHERE id = ?";
- return (Account) jdbcTemplate.queryForObject(sql, new Object[] { id },
- accountRowMap);
- }
写完这两段代码不由感慨,我曾经就这么噼里啪啦的敲了一年多这样的代码。不断的做绑定、映射、写SQL,直到可以有机会将持久层JDBC实现替换Hibernate、iBatis,我才得以解放!
接着,再调整一下Service实现类
AccountServiceImpl.java
- public class AccountServiceImpl implements AccountService {
-
- @Autowired
- private AccountDao accountDao;
-
- @Override
- public Account read(String username, String password) {
- Account account = accountDao.read(username);
-
- if (!password.equals(account.getPassword())) {
- account = null;
- }
- return account;
- }
-
- @Override
- public Account read(int id) {
- return accountDao.read(id);
- }
- }
使用AccountDao接口来完成响应的操作,逻辑部分不做详述,根据业务逻辑而定!
稍稍修改一下profile.jsp,将用户的生日、邮件地址都输出出来!
- <fieldset><legend>用户信息</legend>
- <ul>
- <li><label>用户名:</label><c:out value="${account.username}" /></li>
- <li><label>生日:</label><fmt:formatDate value="${account.birthday}"
- pattern="yyyy年MM月dd日" /></li>
- <li><label>Email:</label><c:out value="${account.email}" /></li>
- </ul>
- </fieldset>
- 标签<fmt:formatDate />用于格式化输出,大家可以了解一下,很简单很好用的标签! :D
启动应用,登录,查看用户信息:
相关代码见附件!
控制器层、持久层都有了一些介绍,剩下的就是业务层了!
业务层中的关键问题在于事务控制!Spring的注解式事务处理其实很简单!
这里将用到以下几个包:
引用
aopalliance-1.0.jar
commons-collections.jar
commons-dbcp.jar
commons-logging-1.1.1.jar
commons-pool.jar
jstl.jar
log4j-1.2.15.jar
mysql-connector-java-5.1.6-bin.jar
spring-aop-2.5.6.jar
spring-beans-2.5.6.jar
spring-context-2.5.6.jar
spring-context-support-2.5.6.jar
spring-core-2.5.6.jar
spring-jdbc-2.5.6.jar
spring-tx-2.5.6.jar
spring-web-2.5.6.jar
spring-webmvc-2.5.6.jar
standard.jar
主要增加了spring-aop-2.5.6.jar的AOP支持包!
之前我们在AccountService中加入了注解
@Transactional
标签,但是要想要真正发挥事务作用,还需要一些配置。
主要需要调整dao.xml文件
dao.xml-事务管理
- <bean
- id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
- p:dataSource-ref="dataSource" />
- <tx:annotation-driven
- transaction-manager="transactionManager" />
细化一下AccountService接口方法
AccountService.java
-
-
-
- package org.zlex.spring.service;
-
- import org.springframework.dao.DataAccessException;
- import org.springframework.transaction.annotation.Transactional;
- import org.zlex.spring.domain.Account;
-
-
-
-
-
-
-
-
- public interface AccountService {
-
-
-
-
-
-
-
-
- @Transactional(readOnly = true)
- Account read(String username, String password);
-
-
-
-
-
-
-
- @Transactional(readOnly = true)
- Account read(int id);
-
-
-
-
-
-
-
- @Transactional(readOnly = false, rollbackFor = DataAccessException.class)
- Account register(Account account);
- }
这里我把注解
@Transactional
调整到了具体的方法上,也就是说这样写的话,凡是加入注解的标注的方法都属于事务配置!
Account register(Account account);
用做用户注册作用!
@Transactional(readOnly = true)
只读属性
@Transactional(readOnly = false, rollbackFor = DataAccessException.class)
只读关闭,遇到DataAccessException异常回滚!如果不对异常进行处理,该异常将一直向上层抛出,直至抛出到页面!
如果你的Eclipse集成了SpringIDE,你可以观察一下这时的xml配置文件和AccoutServiceImpl.java的变化!
这次,来个用户注册功能演示,故意在某个位置制造一个异常,看看是否正常回滚!
先看注册控制器
RegisterController.java
-
-
-
- package org.zlex.spring.controller;
-
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.Date;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.propertyeditors.CustomDateEditor;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.ModelMap;
- import org.springframework.web.bind.WebDataBinder;
- import org.springframework.web.bind.annotation.InitBinder;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.zlex.spring.domain.Account;
- import org.zlex.spring.service.AccountService;
-
-
-
-
-
-
-
-
- @Controller
- @RequestMapping(value = "/register.do")
- public class RegisterController {
-
- @Autowired
- private AccountService accountService;
-
- @InitBinder
- public void initBinder(WebDataBinder binder) {
-
-
-
- DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
- binder.registerCustomEditor(Date.class, "birthday",
- new CustomDateEditor(format, true));
- }
-
- @RequestMapping(method = RequestMethod.GET)
- public String initForm(ModelMap model) {
- Account account = new Account();
- model.addAttribute("account", account);
-
- return "account/register";
- }
-
- @RequestMapping(method = RequestMethod.POST)
- protected String submit(@ModelAttribute("account") Account account) {
- int id = accountService.register(account).getId();
-
- return "redirect:profile.do?id=" + id;
- }
- }
@InitBinder
用于表单自定义属性绑定。这里我们要求输入一个日期格式的生日。
@RequestMapping(method = RequestMethod.GET)
用于初始化页面。
@RequestMapping(method = RequestMethod.POST)
用于提交页面。
再看注册页面
register.jsp
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>注册</title>
- <link rel="stylesheet" type="text/css" href="css/style.css" />
- <script type="text/javascript" src="js/calendar.js"></script>
- </head>
- <body>
- <fieldset><legend>用户注册</legend><form:form
- commandName="account">
- <ul>
- <li><form:label path="username">用户名:</form:label><form:input
- path="username" /></li>
- <li><form:label path="password">密码:</form:label><form:password
- path="password" /></li>
- <li><form:label path="birthday">生日:</form:label><form:input
- path="birthday" onfocus="showDate(this);" /></li>
- <li><form:label path="email">Email:</form:label><form:input
- path="email" /></li>
- <li>
- <button type="submit">注册</button>
- <button type="reset">重置</button>
- </li>
- </ul>
- </form:form></fieldset>
- </body>
- </html>
这里我用了一个JavaScript日期控制标签:
- <script type="text/javascript" src="js/calendar.js"></script>
使用起来就像是这样:
非常好用!!!
当然,你完全可以使用JE上的那个JS控件!
接下来稍微调整一下AccountService接口及其实现AccountServiceImpl
AccountService.java
- public interface AccountService {
-
-
-
-
-
-
-
- @Transactional(readOnly = false, rollbackFor = DataAccessException.class)
- Account register(Account account);
-
- }
- @Service
- public class AccountServiceImpl implements AccountService {
-
- @Autowired
- private AccountDao accountDao;