Spring 注解学习手札

先来构建一个极为简单的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代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns="http://java.sun.com/xml/ns/javaee"  
  5.     xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
  6.     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
  7.     id="WebApp_ID"  
  8.     version="2.5">  
  9.     <display-name>spring</display-name>  
  10.     <!-- 应用路径 -->  
  11.     <context-param>  
  12.         <param-name>webAppRootKey</param-name>  
  13.         <param-value>spring.webapp.root</param-value>  
  14.     </context-param>  
  15.     <!-- Log4J 配置  -->  
  16.     <context-param>  
  17.         <param-name>log4jConfigLocation</param-name>  
  18.         <param-value>classpath:log4j.xml</param-value>  
  19.     </context-param>  
  20.     <context-param>  
  21.         <param-name>log4jRefreshInterval</param-name>  
  22.         <param-value>60000</param-value>  
  23.     </context-param>  
  24.     <!--Spring上下文 配置  -->  
  25.     <context-param>  
  26.         <param-name>contextConfigLocation</param-name>  
  27.         <param-value>/WEB-INF/applicationContext.xml</param-value>  
  28.     </context-param>  
  29.     <!-- 字符集 过滤器  -->  
  30.     <filter>  
  31.         <filter-name>CharacterEncodingFilter</filter-name>  
  32.         <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
  33.         <init-param>  
  34.             <param-name>encoding</param-name>  
  35.             <param-value>UTF-8</param-value>  
  36.         </init-param>  
  37.         <init-param>  
  38.             <param-name>forceEncoding</param-name>  
  39.             <param-value>true</param-value>  
  40.         </init-param>  
  41.     </filter>  
  42.     <filter-mapping>  
  43.         <filter-name>CharacterEncodingFilter</filter-name>  
  44.         <url-pattern>/*</url-pattern>  
  45.     </filter-mapping>  
  46.     <!-- Spring 监听器 -->  
  47.     <listener>  
  48.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  49.     </listener>  
  50.     <listener>  
  51.         <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
  52.     </listener>  
  53.     <!-- Spring 分发器 -->  
  54.     <servlet>  
  55.         <servlet-name>spring</servlet-name>  
  56.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  57.         <init-param>  
  58.             <param-name>contextConfigLocation</param-name>  
  59.             <param-value>/WEB-INF/servlet.xml</param-value>  
  60.         </init-param>  
  61.     </servlet>  
  62.     <servlet-mapping>  
  63.         <servlet-name>spring</servlet-name>  
  64.         <url-pattern>*.do</url-pattern>  
  65.     </servlet-mapping>  
  66.     <welcome-file-list>  
  67.         <welcome-file>index.html</welcome-file>  
  68.         <welcome-file>index.htm</welcome-file>  
  69.         <welcome-file>index.jsp</welcome-file>  
  70.         <welcome-file>default.html</welcome-file>  
  71.         <welcome-file>default.htm</welcome-file>  
  72.         <welcome-file>default.jsp</welcome-file>  
  73.     </welcome-file-list>  
  74. </web-app>  

有不少人问我,这段代码是什么: 
Xml代码   收藏代码
  1. <!-- 应用路径 -->  
  2. <context-param>  
  3.     <param-name>webAppRootKey</param-name>  
  4.     <param-value>spring.webapp.root</param-value>  
  5. </context-param>  

这是当前应用的路径变量,也就是说你可以在其他代码中使用${spring.webapp.root}指代当前应用路径。我经常用它来设置log的输出目录。 
为什么要设置参数log4jConfigLocation? 
Xml代码   收藏代码
  1. <!-- Log4J 配置  -->  
  2.     <context-param>  
  3.         <param-name>log4jConfigLocation</param-name>  
  4.         <param-value>classpath:log4j.xml</param-value>  
  5.     </context-param>  
  6.     <context-param>  
  7.         <param-name>log4jRefreshInterval</param-name>  
  8.         <param-value>60000</param-value>  
  9.     </context-param>  

这是一种基本配置,spring中很多代码使用了不同的日志接口,既有log4j也有commons-logging,这里只是强制转换为log4j!并且,log4j的配置文件只能放在classpath根路径。同时,需要通过commons-logging配置将日志控制权转交给log4j。同时commons-logging.properties必须放置在classpath根路径。 
commons-logging内容: 
Properties代码   收藏代码
  1. org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger  

最后,记得配置log4j的监听器: 
Xml代码   收藏代码
  1. <listener>  
  2.     <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
  3. </listener>  

接下来,我们需要配置两套配置文件,applicationContext.xml和servlet.xml。 
applicationContext.xml用于对应用层面做整体控制。按照分层思想,统领service层和dao层。servlet.xml则单纯控制controller层。 
Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xmlns:context="http://www.springframework.org/schema/context"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  7.         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
  8.     <import  
  9.         resource="service.xml" />  
  10.     <import  
  11.         resource="dao.xml" />  
  12. </beans>  

applicationContext.xml什么都不干,它只管涉及到整体需要的配置,并且集中管理。 
这里引入了两个配置文件service.xml和dao.xml分别用于业务、数据处理。 
service.xml 
Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xmlns:context="http://www.springframework.org/schema/context"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  7.         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
  8.     <context:component-scan  
  9.         base-package="org.zlex.spring.service" />  
  10. </beans>  

注意,这里通过<context:component-scan />标签指定了业务层的基础包路径——“org.zlex.spring.service”。也就是说,业务层相关实现均在这一层。这是有必要的分层之一。 
dao.xml 
Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xmlns:aop="http://www.springframework.org/schema/aop"  
  6.     xmlns:context="http://www.springframework.org/schema/context"  
  7.     xmlns:tx="http://www.springframework.org/schema/tx"  
  8.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  9.         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd  
  10.         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd  
  11.         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">  
  12.     <context:component-scan  
  13.         base-package="org.zlex.spring.dao" />  
  14. </beans>  

dao层如法炮制,包路径是"org.zlex.spring.dao"。从这个角度看,注解还是很方便的!  
最后,我们看看servlet.xml 
Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xmlns:context="http://www.springframework.org/schema/context"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  7.         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
  8.     <context:component-scan  
  9.         base-package="org.zlex.spring.controller" />  
  10.     <bean  
  11.         id="urlMapping"  
  12.         class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />  
  13.     <bean  
  14.         class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />  
  15. </beans>  

包路径配置就不细说了,都是一个概念。最重要的时候后面两个配置,这将使得注解生效!  
“org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping”是默认实现,可以不写,Spring容器默认会默认使用该类。 
“org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter”直接关系到多动作控制器配置是否可用!
 
简单看一下代码结构,如图: 
Spring 注解学习手札_第1张图片  
Account类是来存储账户信息,属于域对象,极为简单,代码如下所示: 
Account.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.domain;  
  5.   
  6. import java.io.Serializable;  
  7.   
  8. /** 
  9.  *  
  10.  * @author <a href="mailto:[email protected]">梁栋</a> 
  11.  * @version 1.0 
  12.  * @since 1.0 
  13.  */  
  14. public class Account implements Serializable {  
  15.   
  16.     /** 
  17.      *  
  18.      */  
  19.     private static final long serialVersionUID = -533698031946372178L;  
  20.   
  21.     private String username;  
  22.     private String password;  
  23.   
  24.     /** 
  25.      * @param username 
  26.      * @param password 
  27.      */  
  28.     public Account(String username, String password) {  
  29.         this.username = username;  
  30.         this.password = password;  
  31.     }  
  32.   
  33.     /** 
  34.      * @return the username 
  35.      */  
  36.     public String getUsername() {  
  37.         return username;  
  38.     }  
  39.   
  40.     /** 
  41.      * @param username the username to set 
  42.      */  
  43.     public void setUsername(String username) {  
  44.         this.username = username;  
  45.     }  
  46.   
  47.     /** 
  48.      * @return the password 
  49.      */  
  50.     public String getPassword() {  
  51.         return password;  
  52.     }  
  53.   
  54.     /** 
  55.      * @param password the password to set 
  56.      */  
  57.     public void setPassword(String password) {  
  58.         this.password = password;  
  59.     }  
  60.       
  61.       
  62. }  

通常,在构建域对象时,需要考虑该对象可能需要进行网络传输,本地缓存,因此建议实现序列化接口Serializable   
我们再来看看控制器,这就稍微复杂了一点代码如下所示: 
AccountController .java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.controller;  
  5.   
  6. import javax.servlet.http.HttpServletRequest;  
  7. import javax.servlet.http.HttpServletResponse;  
  8.   
  9. import org.springframework.beans.factory.annotation.Autowired;  
  10. import org.springframework.stereotype.Controller;  
  11. import org.springframework.web.bind.ServletRequestUtils;  
  12. import org.springframework.web.bind.annotation.RequestMapping;  
  13. import org.springframework.web.bind.annotation.RequestMethod;  
  14. import org.zlex.spring.service.AccountService;  
  15.   
  16. /** 
  17.  *  
  18.  * @author <a href="mailto:[email protected]">梁栋</a> 
  19.  * @version 1.0 
  20.  * @since 1.0 
  21.  */  
  22. @Controller  
  23. @RequestMapping("/account.do")  
  24. public class AccountController {  
  25.   
  26.     @Autowired  
  27.     private AccountService accountService;  
  28.   
  29.     @RequestMapping(method = RequestMethod.GET)  
  30.     public void hello(HttpServletRequest request, HttpServletResponse response)  
  31.             throws Exception {  
  32.   
  33.         String username = ServletRequestUtils.getRequiredStringParameter(  
  34.                 request, "username");  
  35.         String password = ServletRequestUtils.getRequiredStringParameter(  
  36.                 request, "password");  
  37.         System.out.println(accountService.verify(username, password));  
  38.     }  
  39. }  

分段详述: 
Java代码   收藏代码
  1. @Controller  
  2. @RequestMapping("/account.do")  

这两行注解, @Controller 是告诉Spring容器,这是一个控制器类, @RequestMapping("/account.do") 是来定义该控制器对应的请求路径(/account.do) 
Java代码   收藏代码
  1. @Autowired  
  2. 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一统天下的意思!  大家知道就可以了,具体使用何种标准由项目说了算!  
最后,来看看核心方法: 
Java代码   收藏代码
  1. @RequestMapping(method = RequestMethod.GET)  
  2. public void hello(HttpServletRequest request, HttpServletResponse response)  
  3.         throws Exception {  
  4.   
  5.     String username = ServletRequestUtils.getRequiredStringParameter(  
  6.             request, "username");  
  7.     String password = ServletRequestUtils.getRequiredStringParameter(  
  8.             request, "password");  
  9.     System.out.println(accountService.verify(username, password));  
  10. }  

注解@RequestMapping(method = RequestMethod.GET)指定了访问方法类型。 
注意,如果没有用这个注解标识方法,Spring容器将不知道那个方法可以用于处理get请求!  
对于方法名,我们可以随意定!方法中的参数,类似于“HttpServletRequest request, HttpServletResponse response”,只要你需要方法可以是有参也可以是无参! 
解析来看Service层,分为接口和实现: 
AccountService.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.service;  
  5.   
  6. /** 
  7.  *  
  8.  * @author <a href="mailto:[email protected]">梁栋</a> 
  9.  * @version 1.0 
  10.  * @since 1.0 
  11.  */  
  12. public interface AccountService {  
  13.   
  14.     /** 
  15.      * 验证用户身份 
  16.      *  
  17.      * @param username 
  18.      * @param password 
  19.      * @return 
  20.      */  
  21.     boolean verify(String username, String password);  
  22.   
  23. }  

接口不需要任何Spring注解相关的东西,它就是一个简单的接口! 
重要的部分在于实现层,如下所示: 
AccountServiceImpl.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.service.impl;  
  5.   
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.stereotype.Service;  
  8. import org.springframework.transaction.annotation.Transactional;  
  9. import org.zlex.spring.dao.AccountDao;  
  10. import org.zlex.spring.domain.Account;  
  11. import org.zlex.spring.service.AccountService;  
  12.   
  13. /** 
  14.  *  
  15.  * @author <a href="mailto:[email protected]">梁栋</a> 
  16.  * @version 1.0 
  17.  * @since 1.0 
  18.  */  
  19. @Service  
  20. @Transactional  
  21. public class AccountServiceImpl implements AccountService {  
  22.   
  23.     @Autowired  
  24.     private AccountDao accountDao;  
  25.   
  26.     /* 
  27.      * (non-Javadoc) 
  28.      *  
  29.      * @see org.zlex.spring.service.AccountService#verify(java.lang.String, 
  30.      * java.lang.String) 
  31.      */  
  32.     @Override  
  33.     public boolean verify(String username, String password) {  
  34.   
  35.         Account account = accountDao.read(username);  
  36.   
  37.         if (password.equals(account.getPassword())) {  
  38.             return true;  
  39.         } else {  
  40.             return false;  
  41.         }  
  42.     }  
  43.   
  44. }  

注意以下内容: 
Java代码   收藏代码
  1. @Service  
  2. @Transactional  

注解@Service用于标识这是一个Service层实现,@Transactional用于控制事务,将事务定位在业务层,这是非常务实的做法!  
接下来,我们来看持久层:AccountDao和AccountDaoImpl类 
AccountDao.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.dao;  
  5.   
  6. import org.zlex.spring.domain.Account;  
  7.   
  8. /** 
  9.  *  
  10.  * @author <a href="mailto:[email protected]">梁栋</a> 
  11.  * @version 1.0 
  12.  * @since 1.0 
  13.  */  
  14. public interface AccountDao {  
  15.   
  16.     /** 
  17.      * 读取用户信息 
  18.      *  
  19.      * @param username 
  20.      * @return 
  21.      */  
  22.     Account read(String username);  
  23.   
  24. }  

这个接口就是简单的数据提取,无需任何Spring注解有关的东西! 
再看其实现类: 
AccountDaoImpl.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.dao.impl;  
  5.   
  6. import org.springframework.stereotype.Repository;  
  7. import org.zlex.spring.dao.AccountDao;  
  8. import org.zlex.spring.domain.Account;  
  9.   
  10. /** 
  11.  *  
  12.  * @author <a href="mailto:[email protected]">梁栋</a> 
  13.  * @version 1.0 
  14.  * @since 1.0 
  15.  */  
  16. @Repository  
  17. public class AccountDaoImpl implements AccountDao {  
  18.   
  19.     /* (non-Javadoc) 
  20.      * @see org.zlex.spring.dao.AccountDao#read(java.lang.String) 
  21.      */  
  22.     @Override  
  23.     public Account read(String username) {  
  24.        
  25.         return new Account(username,"wolf");  
  26.     }  
  27.   
  28. }  

这里只需要注意注解: 
Java代码   收藏代码
  1. @Repository  

意为持久层,Dao实现这层我没有过于细致的介绍通过注解调用ORM或是JDBC来完成实现,这些内容后续细述! 
这里我们没有提到注解 @Component ,共有4种“组件”式注解: 
引用
  
@Component:可装载组件  
@Repository:持久层组件  
@Service:服务层组件  
@Controller:控制层组件 

这样spring容器就完成了控制层、业务层和持久层的构建。 
启动应用,访问 http://localhost:8080/spring/account.do?username=snow&password=wolf  
观察控制台,如果得到包含“true”的输出,本次构建就成功了!  
Spring 注解学习手札_第2张图片  
代码见附件!  

顺便说一句:在Spring之前的XML配置中,如果你想在一个类中获得文件可以通过在xml配置这个类的某个属性。在注解的方式(Spring3.0)中,你可以使用 @Value 来指定这个文件。 
例如,我们想要在一个类中获得一个文件,可以这样写: 
Java代码   收藏代码
  1. @Value("/WEB-INF/database.properties")  
  2. private File databaseConfig;  


如果这个properties文件已经正常在容器中加载,可以直接这样写: 

Java代码   收藏代码
  1. @Value("${jdbc.url}")   
  2. private String url;  

获得这个url参数!   

容器中加载这个Properties文件: 
Xml代码   收藏代码
  1. <util:properties id="jdbc" location="/WEB-INF/database.properties"/>  


这样,我们就能通过注解 @Value 获得 /WEB-INF/database.properties 这个文件!  
如果我们想要获得注入在xml中的某个类,例如dataSource(<bean id ="dataSource">)可以在注解的类中这么写: 
Java代码   收藏代码
  1. @Resource(name = "dataSource")  
  2. private BasicDataSource dataSource;  


如果只有这么一个类使用该配置文件: 
Java代码   收藏代码
  1. @ImportResource("/WEB-INF/database.properties")  
  2. 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 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.controller;  
  5.   
  6. import javax.servlet.http.HttpServletRequest;  
  7. import javax.servlet.http.HttpServletResponse;  
  8.   
  9. import org.springframework.beans.factory.annotation.Autowired;  
  10. import org.springframework.stereotype.Controller;  
  11. import org.springframework.web.bind.ServletRequestUtils;  
  12. import org.springframework.web.bind.annotation.RequestMapping;  
  13. import org.springframework.web.bind.annotation.RequestMethod;  
  14. import org.zlex.spring.service.AccountService;  
  15.   
  16. /** 
  17.  *  
  18.  * @author <a href="mailto:[email protected]">梁栋</a> 
  19.  * @version 1.0 
  20.  * @since 1.0 
  21.  */  
  22. @Controller  
  23. @RequestMapping("/account.do")  
  24. public class AccountController {  
  25.   
  26.     @Autowired  
  27.     private AccountService accountService;  
  28.   
  29.     @RequestMapping(method = RequestMethod.GET)  
  30.     public void hello(HttpServletRequest request, HttpServletResponse response)  
  31.             throws Exception {  
  32.   
  33.         String username = ServletRequestUtils.getRequiredStringParameter(  
  34.                 request, "username");  
  35.         String password = ServletRequestUtils.getRequiredStringParameter(  
  36.                 request, "password");  
  37.         System.out.println(accountService.verify(username, password));  
  38.     }  
  39. }  

先说注解 @RequestMapping  
这里使用注解 @RequestMapping(method = RequestMethod.GET) 指定这个方法为get请求时调用。同样,我们可以使用注解 @RequestMapping(method = RequestMethod.POST) 指定该方法接受post请求。 
Java代码   收藏代码
  1. @Controller  
  2. @RequestMapping("/account.do")  
  3. public class AccountController {  
  4.   
  5.     @RequestMapping(method = RequestMethod.GET)  
  6.     public void get() {  
  7.     }  
  8.   
  9.     @RequestMapping(method = RequestMethod.POST)  
  10.     public void post() {  
  11.     }  
  12. }  

这与我们久别的Servlet很相像,类似于doGet()和doPost()方法! 
我们也可以将其改造为多动作控制器,如下代码所示: 
Java代码   收藏代码
  1. @Controller  
  2. @RequestMapping("/account.do")  
  3. public class AccountController {  
  4.   
  5.     @RequestMapping(params = "method=login")    
  6.     public void login() {  
  7.     }  
  8.   
  9.     @RequestMapping(params = "method=logout")    
  10.     public void logout() {  
  11.     }  

这样,我们可以通过参数“method”指定不同的参数值从而通过请求("/account.do?method=login"和"/account.do?method=logout")调用不同的方法!  
注意:使用多动作控制器必须在配置文件中加入注解支持!  
Xml代码   收藏代码
  1. <bean  
  2.         class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />  

当然,我们也可以将注解 @RequestMapping 指定到某一个方法上,如: 
Java代码   收藏代码
  1. @Controller  
  2. public class AccountController {  
  3.       
  4.     @RequestMapping("/a.do")  
  5.     public void a() {}  
  6.   
  7.     @RequestMapping("/b.do")  
  8.     public void b() {}  
  9. }  

这样,请求“a.do”和“b.do”将对应不同的方法a() 和b()。这使得一个控制器可以同时承载多个请求! 
@RequestMapping("/account.do") @RequestMapping(value="/account.do") 的简写! 
再说输入参数! 
这里的方法名可以随意定义,但是参数和返回值却又要求! 
为什么?直接看源代码,我们就能找到答案!  
AnnotationMethodHandlerAdapter.java部分源代码——有关参数部分: 
Java代码   收藏代码
  1. @Override  
  2. protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest)  
  3.         throws Exception {  
  4.   
  5.     HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();  
  6.     HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();  
  7.   
  8.     if (ServletRequest.class.isAssignableFrom(parameterType)) {  
  9.         return request;  
  10.     }  
  11.     else if (ServletResponse.class.isAssignableFrom(parameterType)) {  
  12.         this.responseArgumentUsed = true;  
  13.         return response;  
  14.     }  
  15.     else if (HttpSession.class.isAssignableFrom(parameterType)) {  
  16.         return request.getSession();  
  17.     }  
  18.     else if (Principal.class.isAssignableFrom(parameterType)) {  
  19.         return request.getUserPrincipal();  
  20.     }  
  21.     else if (Locale.class.equals(parameterType)) {  
  22.         return RequestContextUtils.getLocale(request);  
  23.     }  
  24.     else if (InputStream.class.isAssignableFrom(parameterType)) {  
  25.         return request.getInputStream();  
  26.     }  
  27.     else if (Reader.class.isAssignableFrom(parameterType)) {  
  28.         return request.getReader();  
  29.     }  
  30.     else if (OutputStream.class.isAssignableFrom(parameterType)) {  
  31.         this.responseArgumentUsed = true;  
  32.         return response.getOutputStream();  
  33.     }  
  34.     else if (Writer.class.isAssignableFrom(parameterType)) {  
  35.         this.responseArgumentUsed = true;  
  36.         return response.getWriter();  
  37.     }  
  38.     return super.resolveStandardArgument(parameterType, webRequest);  
  39. }  

也就是说,如果我们想要在自定义的方法中获得一些个“标准”输入参数,参数类型必须包含在以下类型中: 
引用

ServletRequest 
ServletResponse 
HttpSession 
Principal 
Locale 
InputStream 
OutputStream 
Reader 
Writer 

当然,上述接口其实都是对于HttpServletRequest和HttpServletResponse的扩展。 
此外,我们还可以定义自己的参数。 
注意:自定义参数必须是实现类,绝非接口!Spring容器将帮你完成对象初始化工作!  
比如说上文中,我们需要参数username和password。我们可以这么写: 
Java代码   收藏代码
  1. @RequestMapping(method = RequestMethod.GET)  
  2. public void hello(String username,String password) {  
  3.     System.out.println(accountService.verify(username, password));  
  4. }  

如果参数名不能与这里的变量名保持一致,那么我们可以使用注解 @RequestParam 进行强制绑定,代码如下所示: 
Java代码   收藏代码
  1. @RequestMapping(method = RequestMethod.GET)  
  2. public void hello(@RequestParam("username") String u,  
  3.         @RequestParam("password") String p) {  
  4.     System.out.println(accountService.verify(u, p));  
  5. }  

这比起我们之前写的代码有所简洁: 
Java代码   收藏代码
  1. @RequestMapping(method = RequestMethod.GET)  
  2. public void hello(HttpServletRequest request, HttpServletResponse response)  
  3.         throws Exception {  
  4.   
  5.     String username = ServletRequestUtils.getRequiredStringParameter(  
  6.             request, "username");  
  7.     String password = ServletRequestUtils.getRequiredStringParameter(  
  8.             request, "password");  
  9.     System.out.println(accountService.verify(username, password));  
  10. }  

ServletRequestUtils类的工作已经由Spring底层实现了,我们只需要把参数名定义一致即可,其内部取参无需关心!  
除了传入参数,我们还可以定义即将传出的参数,如加入ModelMap参数: 
Java代码   收藏代码
  1. @SuppressWarnings("unchecked")  
  2. @RequestMapping(method = RequestMethod.GET)  
  3. public Map hello(String username, String password, ModelMap model) {  
  4.   
  5.     System.out.println(accountService.verify(username, password));  
  6.       
  7.     model.put("msg", username);  
  8.   
  9.     return model;  
  10. }  

这时,我们没有定义页面名称,Spring容器将根据请求名指定同名view,即如果是jap页面,则account.do->account.jsp! 
不得不承认,这样写起来的确减少了代码量!  
接着说输出参数! 
通过ModelMap,我们可以绑定输出到的页面的参数,但最终我们将要返回到何种页面呢?再次查看AnnotationMethodHandlerAdapter源代码! 
AnnotationMethodHandlerAdapter.java部分源代码——有关返回值部分: 
Java代码   收藏代码
  1. @SuppressWarnings("unchecked")  
  2. public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue,  
  3.         ExtendedModelMap implicitModel, ServletWebRequest webRequest) {  
  4.   
  5.     if (returnValue instanceof ModelAndView) {  
  6.         ModelAndView mav = (ModelAndView) returnValue;  
  7.         mav.getModelMap().mergeAttributes(implicitModel);  
  8.         return mav;  
  9.     }  
  10.     else if (returnValue instanceof Model) {  
  11.         return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());  
  12.     }  
  13.     else if (returnValue instanceof Map) {  
  14.         return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue);  
  15.     }  
  16.     else if (returnValue instanceof View) {  
  17.         return new ModelAndView((View) returnValue).addAllObjects(implicitModel);  
  18.     }  
  19.     else if (returnValue instanceof String) {  
  20.         return new ModelAndView((String) returnValue).addAllObjects(implicitModel);  
  21.     }  
  22.     else if (returnValue == null) {  
  23.         // Either returned null or was 'void' return.  
  24.         if (this.responseArgumentUsed || webRequest.isNotModified()) {  
  25.             return null;  
  26.         }  
  27.         else {  
  28.             // Assuming view name translation...  
  29.             return new ModelAndView().addAllObjects(implicitModel);  
  30.         }  
  31.     }  
  32.     else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {  
  33.         // Assume a single model attribute...  
  34.         ModelAttribute attr = AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class);  
  35.         String attrName = (attr != null ? attr.value() : "");  
  36.         ModelAndView mav = new ModelAndView().addAllObjects(implicitModel);  
  37.         if ("".equals(attrName)) {  
  38.             Class resolvedType = GenericTypeResolver.resolveReturnType(handlerMethod, handlerType);  
  39.             attrName = Conventions.getVariableNameForReturnType(handlerMethod, resolvedType, returnValue);  
  40.         }  
  41.         return mav.addObject(attrName, returnValue);  
  42.     }  
  43.     else {  
  44.         throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);  
  45.     }  
  46. }  

返回值的定义十分庞大,或者说可怕的if-else多少有点让我觉得厌恶!  
我们可以定义以下类型的返回值: 
引用

ModelAndView 
Model 
View 
Map 
String 
null 

ModelAndView、Model和View都是Spring之前版本所特有的元素,Map对应于传入参数ModelMap,String定义页面名称,null即对应void类型方法! 
最常用的实现方式如下: 
Java代码   收藏代码
  1. @SuppressWarnings("unchecked")  
  2. @RequestMapping(method = RequestMethod.GET)  
  3. public String hello(String username, String password, ModelMap model) {  
  4.   
  5.     System.out.println(accountService.verify(username, password));  
  6.   
  7.     model.put("msg", username);  
  8.   
  9.     return "account";  
  10. }  

当然,对于我来说在返回值中写入这么一个字符串多少有点不能接受,于是我还是乐于使用输入参数ModelMap+输出参数Map的方式。 
给出一个完整的AccountController实现: 
AccountController.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.controller;  
  5.   
  6. import java.util.Map;  
  7.   
  8. import org.springframework.beans.factory.annotation.Autowired;  
  9. import org.springframework.stereotype.Controller;  
  10. import org.springframework.ui.ModelMap;  
  11. import org.springframework.web.bind.annotation.RequestMapping;  
  12. import org.springframework.web.bind.annotation.RequestMethod;  
  13. import org.zlex.spring.service.AccountService;  
  14.   
  15. /** 
  16.  *  
  17.  * @author <a href="mailto:[email protected]">梁栋</a> 
  18.  * @version 1.0 
  19.  * @since 1.0 
  20.  */  
  21. @Controller  
  22. @RequestMapping("/account.do")  
  23. public class AccountController {  
  24.   
  25.     @Autowired  
  26.     private AccountService accountService;  
  27.   
  28.     @SuppressWarnings("unchecked")  
  29.     @RequestMapping(method = RequestMethod.GET)  
  30.     public Map hello(String username, String password, ModelMap model) {  
  31.   
  32.         System.out.println(accountService.verify(username, password));  
  33.   
  34.         model.put("msg", username);  
  35.         return model;  
  36.     }  
  37. }  

最后说注解 @Session  
如果想将某个ModelMap中的参数指定到Session中,可以使用 @Session 注解,将其绑定为Session熟悉,代码如下所示: 
Java代码   收藏代码
  1. @Controller  
  2. @RequestMapping("/account.do")  
  3. @SessionAttributes("msg")  
  4. public class AccountController {  
  5.   
  6.     @Autowired  
  7.     private AccountService accountService;  
  8.   
  9.     @SuppressWarnings("unchecked")  
  10.     @RequestMapping(method = RequestMethod.GET)  
  11.     public Map hello(String username, String password, ModelMap model) {  
  12.   
  13.         System.out.println(accountService.verify(username, password));  
  14.   
  15.         model.put("msg", username);  
  16.         return model;  
  17.     }  
  18.   
  19. }  

当然,我们还需要配置一下对应的视图解析器,给出完整配置: 
servelt.xml 
Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xmlns:p="http://www.springframework.org/schema/p"  
  6.     xmlns:context="http://www.springframework.org/schema/context"  
  7.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  8.         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
  9.     <context:component-scan  
  10.         base-package="org.zlex.spring.controller" />  
  11.     <bean  
  12.         id="urlMapping"  
  13.         class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />  
  14.     <bean  
  15.         class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />  
  16.     <bean  
  17.         id="jstlViewResolver"  
  18.         class="org.springframework.web.servlet.view.InternalResourceViewResolver"  
  19.         p:viewClass="org.springframework.web.servlet.view.JstlView"  
  20.         p:prefix="/WEB-INF/page/"  
  21.         p:suffix=".jsp" />  
  22. </beans>  

这里使用了JstlView作为视图解析器。同时,指定前缀路径为"/WEB-INF/page/",后缀路径为".jsp"。也就是说,Spring容器将会在这个路径中寻找匹配的jsp文件! 
注意加入 xmlns:p="http://www.springframework.org/schema/p" 命名空间! 
再给出页面内容: 
taglib.jsp 
Jsp代码   收藏代码
  1. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>  
  2. <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>  
  3. <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%>  
  4. <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml"%>  
  5. <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>  
  6. <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>  
  7. <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>  

account.jap 
Jsp代码   收藏代码
  1. <%@ page language="java" contentType="text/html; charset=UTF-8"  
  2.     pageEncoding="UTF-8"%>  
  3. <%@ include file="/WEB-INF/page/taglib.jsp"%>  
  4. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">  
  5. <html>  
  6. <head>  
  7. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
  8. <title>Account</title>  
  9. </head>  
  10. <body>  
  11. <c:out value="${msg}"></c:out>  
  12. </body>  
  13. </html>  

目录结构如图所示: 
Spring 注解学习手札_第3张图片  
启动应用,最后将得到一个带有内容的页面,如图: 
Spring 注解学习手札_第4张图片
代码见附件!


如果要说表单,最简单的就是用户登录页面了!估计大多数做B/S出身的兄弟可能写的第一个表单就是登录表单了! 今天,我也不例外,做一个登录验证实现! 
首先,改造一下账户类Account,增加一个id字段: 
Account.java 

Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.domain;  
  5.   
  6. import java.io.Serializable;  
  7.   
  8. /** 
  9.  * 账户 
  10.  *  
  11.  * @author <a href="mailto:[email protected]">梁栋</a> 
  12.  * @version 1.0 
  13.  * @since 1.0 
  14.  */  
  15. public class Account implements Serializable {  
  16.   
  17.     /** 
  18.      *  
  19.      */  
  20.     private static final long serialVersionUID = -533698031946372178L;  
  21.   
  22.     /** 
  23.      * 主键 
  24.      */  
  25.     private int id;  
  26.     /** 
  27.      * 用户名 
  28.      */  
  29.     private String username;  
  30.     /** 
  31.      * 密码 
  32.      */  
  33.     private String password;  
  34.   
  35.       
  36.   
  37.     public Account() {  
  38.     }  
  39.   
  40.     /** 
  41.      * @param id 
  42.      */  
  43.     public Account(int id) {  
  44.         this.id = id;  
  45.     }  
  46.   
  47.      // get、set方法省略  
  48.   
  49. }  

接下来,为了协调逻辑处理,我们改造接口AccountService及其实现类AccountServiceImpl: 
AccountService.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.service;  
  5.   
  6. import org.springframework.transaction.annotation.Transactional;  
  7. import org.zlex.spring.domain.Account;  
  8.   
  9. /** 
  10.  * 账户业务接口 
  11.  *  
  12.  * @author <a href="mailto:[email protected]">梁栋</a> 
  13.  * @version 1.0 
  14.  * @since 1.0 
  15.  */  
  16. @Transactional  
  17. public interface AccountService {  
  18.   
  19.     /** 
  20.      * 获得账户 
  21.      *  
  22.      * @param username 
  23.      * @param password 
  24.      * @return 
  25.      */  
  26.     Account read(String username, String password);  
  27.   
  28.     /** 
  29.      * 获得账户 
  30.      *  
  31.      * @param id 
  32.      * @return 
  33.      */  
  34.     Account read(int id);  
  35. }  

我们暂时抛开AccountDao该做的事情,在AccountServiceImpl中完成数据提取: 
AccountServiceImpl.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.service.impl;  
  5.   
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.stereotype.Service;  
  8. import org.zlex.spring.dao.AccountDao;  
  9. import org.zlex.spring.domain.Account;  
  10. import org.zlex.spring.service.AccountService;  
  11.   
  12. /** 
  13.  * 账户业务 
  14.  *  
  15.  * @author <a href="mailto:[email protected]">梁栋</a> 
  16.  * @version 1.0 
  17.  * @since 1.0 
  18.  */  
  19. @Service  
  20. public class AccountServiceImpl implements AccountService {  
  21.   
  22.     @Autowired  
  23.     private AccountDao accountDao;  
  24.   
  25.     @Override  
  26.     public Account read(String username, String password) {  
  27.         Account account = null;  
  28.         if (username.equals("snowolf") && password.equals("zlex")) {  
  29.             account = new Account();  
  30.             account.setId(1);  
  31.             account.setUsername(username);  
  32.             account.setPassword(password);  
  33.         }  
  34.         return account;  
  35.     }  
  36.   
  37.     @Override  
  38.     public Account read(int id) {  
  39.         Account account = new Account();  
  40.         account.setId(1);  
  41.         account.setUsername("snowolf");  
  42.         account.setPassword("zlex");  
  43.         return account;  
  44.     }  
  45. }  

先来一个账户信息的展示,构建一个控制器ProfileController: 
ProfileController.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-26 
  3.  */  
  4. package org.zlex.spring.controller;  
  5.   
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.stereotype.Controller;  
  8. import org.springframework.ui.ModelMap;  
  9. import org.springframework.web.bind.annotation.RequestMapping;  
  10. import org.springframework.web.bind.annotation.RequestMethod;  
  11. import org.springframework.web.bind.annotation.RequestParam;   
  12. import org.zlex.spring.domain.Account;  
  13. import org.zlex.spring.service.AccountService;  
  14.   
  15. /** 
  16.  * 账户信息控制器 
  17.  *  
  18.  * @author <a href="mailto:[email protected]">梁栋</a> 
  19.  * @version 1.0 
  20.  * @since 1.0 
  21.  */  
  22. @Controller  
  23. @RequestMapping(value = "/profile.do")  
  24. public class ProfileController {  
  25.     @Autowired  
  26.     private AccountService accountService;  
  27.   
  28.     /** 
  29.      * 账户信息展示 
  30.      *  
  31.      * @param id 
  32.      * @param model 
  33.      * @return 
  34.      */  
  35.     @RequestMapping(method = RequestMethod.GET)  
  36.     public String profile(@RequestParam("id"int id, ModelMap model) {  
  37.         Account account = accountService.read(id);  
  38.         model.addAttribute("account", account);  
  39.   
  40.         // 跳转到用户信息页面  
  41.         return "account/profile";  
  42.     }  
  43. }  

@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 
Jsp代码   收藏代码
  1. <fieldset><legend>用户信息</legend>  
  2. <ul>  
  3.     <li><label>用户名:</label><c:out value="${account.username}" /></li>  
  4. </ul>  
  5. </fieldset>  

账户信息已经绑定在response的属性上。自然,使用<c:out />标签就可以获得账户信息内容。 
访问地址 http://localhost:8080/spring/profile.do?id=1 ,结果如图所示: 
Spring 注解学习手札_第5张图片  
接着构建一个登录控制器LoginController 
LoginController.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-25 
  3.  */  
  4. package org.zlex.spring.controller;  
  5.   
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.stereotype.Controller;  
  8. import org.springframework.ui.ModelMap;  
  9. import org.springframework.web.bind.annotation.ModelAttribute;  
  10. import org.springframework.web.bind.annotation.RequestMapping;  
  11. import org.springframework.web.bind.annotation.RequestMethod;  
  12. import org.zlex.spring.domain.Account;  
  13. import org.zlex.spring.service.AccountService;  
  14.   
  15. /** 
  16.  * 登录控制器 
  17.  *  
  18.  * @author <a href="mailto:[email protected]">梁栋</a> 
  19.  * @version 1.0 
  20.  * @since 1.0 
  21.  */  
  22. @Controller  
  23. @RequestMapping(value = "/login.do")  
  24. public class LoginController {  
  25.   
  26.     @Autowired  
  27.     private AccountService accountService;  
  28.   
  29.     /** 
  30.      * 初始化表单 
  31.      *  
  32.      * @param model 
  33.      * @return 
  34.      */  
  35.     @RequestMapping(method = RequestMethod.GET)  
  36.     public String initForm(ModelMap model) {  
  37.         Account account = new Account();  
  38.         model.addAttribute("account", account);  
  39.         // 直接跳转到登录页面  
  40.         return "account/login";  
  41.     }  
  42.   
  43.     /** 
  44.      * 登录 
  45.      *  
  46.      * @param account 
  47.      * @return 
  48.      */  
  49.     @RequestMapping(method = RequestMethod.POST)  
  50.     public String login(@ModelAttribute("account") Account account) {  
  51.         Account acc = accountService.read(account.getUsername(), account  
  52.                 .getPassword());  
  53.         if (acc != null) {  
  54.             return "redirect:profile.do?id=" + acc.getId();  
  55.         } else {  
  56.             return "redirect:login.do";  
  57.         }  
  58.     }  
  59. }  

分段详述,先说初始化表单: 
Java代码   收藏代码
  1. /** 
  2.  * 初始化表单 
  3.  *  
  4.  * @param model 
  5.  * @return 
  6.  */  
  7. @RequestMapping(method = RequestMethod.GET)  
  8. public String initForm(ModelMap model) {  
  9.     Account account = new Account();  
  10.     model.addAttribute("account", account);  
  11.     // 直接跳转到登录页面  
  12.     return "account/login";  
  13. }  

@RequestMapping(method = RequestMethod.GET) 指定了GET请求方式,这与POST表单提交相对应!  
model.addAttribute("account", account); 绑定账户对象,也就是这个登录表单对象 
return "account/login"; 指向登录页面 
再看登录方法: 
Java代码   收藏代码
  1. /** 
  2.  * 登录 
  3.  *  
  4.  * @param account 
  5.  * @return 
  6.  */  
  7. @RequestMapping(method = RequestMethod.POST)  
  8. public String login(@ModelAttribute("account") Account account) {  
  9.     Account acc = accountService.read(account.getUsername(), account  
  10.             .getPassword());  
  11.     if (acc != null) {  
  12.         return "redirect:profile.do?id=" + acc.getId();  
  13.     } else {  
  14.         return "redirect:login.do";  
  15.     }  
  16. }  

@RequestMapping(method = RequestMethod.POST) 绑定POST表单提交请求 
@ModelAttribute("account") Account account 绑定表单对象。 
最后,再来看看页面: 
login.jsp 
Jsp代码   收藏代码
  1. <fieldset><legend>登录</legend><form:form commandName="account">  
  2.     <form:hidden path="id" />  
  3.     <ul>  
  4.         <li><form:label path="username">用户名:</form:label><form:input  
  5.             path="username" /></li>  
  6.         <li><form:label path="password">密码:</form:label><form:password  
  7.             path="password" /></li>  
  8.         <li>  
  9.         <button type="submit">登录</button>  
  10.         <button type="reset">重置</button>  
  11.         </li>  
  12.     </ul>  
  13. </form:form></fieldset>  

注意, <form:form commandName="account"> 必须指明 commandName ,且与表单初始化、提交方法中的表单对象名称保持一致! 
页面目录结构如下图所示: 
 
在页面中,我加入了一部分css效果,这部分代码我就不在这里唠叨了,大家可以看源码!  
登录试试,如图: 
Spring 注解学习手札_第6张图片  
用户名: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,我的最爱!  可惜前途未卜!  
建库: 
Sql代码   收藏代码
  1. CREATE DATABASE `spring` /*!40100 DEFAULT CHARACTER SET utf8 */;  

建表: 
Sql代码   收藏代码
  1. DROP TABLE IF EXISTS `spring`.`account`;  
  2. CREATE TABLE  `spring`.`account` (  
  3.   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  
  4.   `username` varchar(45) NOT NULL,  
  5.   `passwordvarchar(45) NOT NULL,  
  6.   `birthday` datetime NOT NULL,  
  7.   `email` varchar(45) NOT NULL,  
  8.   PRIMARY KEY (`id`)  
  9. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;  

插入默认数据: 
Sql代码   收藏代码
  1. INSERT INTO `spring`.`account`(  
  2.  `username`,  
  3.  `password`,  
  4.  `birthday`,  
  5.  `email`)  
  6. VALUES(  
  7.  'snowolf',  
  8.  'zlex',  
  9.  '2010-01-01',  
  10.  '[email protected]');  

给出一个数据库查询的结果: 
 
很不巧,为了能让数据查询更有意义,我又要改动Account类:  
Account.java 
Java代码   收藏代码
  1. public class Account implements Serializable {  
  2.   
  3.     /** 
  4.      * 主键 
  5.      */  
  6.     private int id;  
  7.       
  8.     /** 
  9.      * 用户名 
  10.      */  
  11.     private String username;  
  12.       
  13.     /** 
  14.      * 密码 
  15.      */  
  16.     private String password;  
  17.   
  18.     /** 
  19.      * 生日 
  20.      */  
  21.     private Date birthday;  
  22.   
  23.     /** 
  24.      * Email 
  25.      */  
  26.     private String email;  
  27.       
  28.         // get方法set方法省略  
  29. }  

这样,域对象与数据库表将完成一一对应绑定关系。 
再建立一个用于构建数据源配置的文件database.properties 
database.properties: 
Properties代码   收藏代码
  1. dataSource.driverClassName=com.mysql.jdbc.Driver  
  2. dataSource.url=jdbc:mysql://localhost:3306/spring  
  3. dataSource.username=root  
  4. dataSource.password=admin  
  5. dataSource.maxActive=200  
  6. dataSource.maxIdle=50  
  7. dataSource.maxWait=10000  

该文件位于/WEB-INF/目录下。 
接下来,我们需要把它引入spring容器,修改applicationContext.xml: 
applicationContext.xml 
Xml代码   收藏代码
  1. <context:property-placeholder  
  2.     location="/WEB-INF/database.properties" />  

如果需要引入多个properties文件,可以用逗号分隔。 
这时,我们已经引入了数据源配置,我们可以通过修改dao.xml构建基于DBCP的数据源: 
dao.xml中dataSource配置 
Xml代码   收藏代码
  1. <bean  
  2.         id="dataSource"  
  3.         class="org.apache.commons.dbcp.BasicDataSource"  
  4.         destroy-method="close"  
  5.         lazy-init="false"  
  6.         p:driverClassName="${dataSource.driverClassName}"  
  7.         p:url="${dataSource.url}"  
  8.         p:username="${dataSource.username}"  
  9.         p:password="${dataSource.password}"  
  10.         p:maxActive="${dataSource.maxActive}"  
  11.         p:maxIdle="${dataSource.maxIdle}"  
  12.         p:maxWait="${dataSource.maxWait}" />  

上述配置稀松平常,没有什么好阐述的内容,这与一般spring配置无异样。 
需要注意的是这个jdbcTemplate配置! 
dao.xml中jdbcTemplate配置 
Xml代码   收藏代码
  1. <bean  
  2.     class="org.springframework.jdbc.core.JdbcTemplate"  
  3.     p:dataSource-ref="dataSource" />  

这个配置很关键,如果你要使用其他的ORM框架,同样需要配置这样的模板类,在Dao实现中无需继承JdbcDaoSupport类。 
不需要明确JdbcTemplate的id(id="jdbcTemplate")吗?不再需要了!  
AccountDao.java 
Java代码   收藏代码
  1. public interface AccountDao {  
  2.   
  3.     /** 
  4.      * 读取账户信息 
  5.      *  
  6.      * @param username 
  7.      * @return 
  8.      */  
  9.     Account read(String username);  
  10.   
  11.     /** 
  12.      * 读取账户信息 
  13.      *  
  14.      * @param id 
  15.      * @return 
  16.      */  
  17.     Account read(int id);  
  18. }  

AccountDaoImpl.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.dao.impl;  
  5.   
  6. import java.sql.ResultSet;  
  7. import java.sql.SQLException;  
  8.   
  9. import org.springframework.beans.factory.annotation.Autowired;  
  10. import org.springframework.jdbc.core.JdbcTemplate;  
  11. import org.springframework.jdbc.core.RowMapper;  
  12. import org.springframework.stereotype.Repository;  
  13. import org.zlex.spring.dao.AccountDao;  
  14. import org.zlex.spring.domain.Account;  
  15.   
  16. /** 
  17.  * 账户数据库实现 
  18.  *  
  19.  * @author <a href="mailto:[email protected]">梁栋</a> 
  20.  * @version 1.0 
  21.  * @since 1.0 
  22.  */  
  23. @Repository  
  24. public class AccountDaoImpl implements AccountDao {  
  25.   
  26.     @Autowired  
  27.     private JdbcTemplate jdbcTemplate;  
  28.   
  29.     @Override  
  30.     public Account read(String username) {  
  31.         String sql = "SELECT * From account WHERE username = ?";  
  32.         return (Account) jdbcTemplate.queryForObject(sql,  
  33.                 new Object[] { username }, accountRowMap);  
  34.     }  
  35.   
  36.     @Override  
  37.     public Account read(int id) {  
  38.         String sql = "SELECT * From account WHERE id = ?";  
  39.         return (Account) jdbcTemplate.queryForObject(sql, new Object[] { id },  
  40.                 accountRowMap);  
  41.     }  
  42.   
  43.     protected RowMapper accountRowMap = new RowMapper() {  
  44.         @Override  
  45.         public Object mapRow(ResultSet rs, int rowNum) throws SQLException {  
  46.             Account account = new Account();  
  47.             account.setId(rs.getInt("id"));  
  48.             account.setUsername(rs.getString("username"));  
  49.             account.setPassword(rs.getString("password"));  
  50.             account.setBirthday(rs.getDate("birthday"));  
  51.             account.setEmail(rs.getString("email"));  
  52.             return account;  
  53.         }  
  54.     };  
  55. }  

分段详述: 
注解 @Repository 明确这个类是用于持久层的实现类,注意这样的注解不能用于接口,仅适用于实现类! 
同时,不再需要继承JdbcDaoSupport类,其而代之的是直接注入JdbcTemplate类!  
再看声明JdbcTemplate类: 
Java代码   收藏代码
  1. @Autowired  
  2. private JdbcTemplate jdbcTemplate;  

需要说明一下,这里的JdbcTemplate对象jdbcTemplate名称可以自定,没有任何限制!  
这里使用 RowMapper 定义了一个用于绑定Account域对象的内部映射类: 
RowMapper 
Java代码   收藏代码
  1. protected RowMapper accountRowMap = new RowMapper() {  
  2.     @Override  
  3.     public Object mapRow(ResultSet rs, int rowNum) throws SQLException {  
  4.         Account account = new Account();  
  5.         account.setId(rs.getInt("id"));  
  6.         account.setUsername(rs.getString("username"));  
  7.         account.setPassword(rs.getString("password"));  
  8.         account.setBirthday(rs.getDate("birthday"));  
  9.         account.setEmail(rs.getString("email"));  
  10.         return account;  
  11.     }  
  12. };  

语句级的内容,十分简单,如下所示: 
Java代码   收藏代码
  1. @Override  
  2. public Account read(String username) {  
  3.     String sql = "SELECT * From account WHERE username = ?";  
  4.     return (Account) jdbcTemplate.queryForObject(sql,  
  5.             new Object[] { username }, accountRowMap);  
  6. }  
  7.   
  8. @Override  
  9. public Account read(int id) {  
  10.     String sql = "SELECT * From account WHERE id = ?";  
  11.     return (Account) jdbcTemplate.queryForObject(sql, new Object[] { id },  
  12.             accountRowMap);  
  13. }  

写完这两段代码不由感慨,我曾经就这么噼里啪啦的敲了一年多这样的代码。不断的做绑定、映射、写SQL,直到可以有机会将持久层JDBC实现替换Hibernate、iBatis,我才得以解放!  
接着,再调整一下Service实现类 
AccountServiceImpl.java 
Java代码   收藏代码
  1. public class AccountServiceImpl implements AccountService {  
  2.   
  3.     @Autowired  
  4.     private AccountDao accountDao;  
  5.   
  6.     @Override  
  7.     public Account read(String username, String password) {  
  8.         Account account = accountDao.read(username);  
  9.   
  10.         if (!password.equals(account.getPassword())) {  
  11.             account = null;  
  12.         }  
  13.         return account;  
  14.     }  
  15.   
  16.     @Override  
  17.     public Account read(int id) {  
  18.         return accountDao.read(id);  
  19.     }  
  20. }  

使用AccountDao接口来完成响应的操作,逻辑部分不做详述,根据业务逻辑而定! 
稍稍修改一下profile.jsp,将用户的生日、邮件地址都输出出来! 
Jsp代码   收藏代码
  1. <fieldset><legend>用户信息</legend>  
  2. <ul>  
  3.     <li><label>用户名:</label><c:out value="${account.username}" /></li>  
  4.     <li><label>生日:</label><fmt:formatDate value="${account.birthday}"  
  5.         pattern="yyyy年MM月dd日" /></li>  
  6.     <li><label>Email:</label><c:out value="${account.email}" /></li>  
  7. </ul>  
  8. </fieldset>  
  9. 标签<fmt:formatDate />用于格式化输出,大家可以了解一下,很简单很好用的标签! :D   

启动应用,登录,查看用户信息: 
Spring 注解学习手札_第7张图片  

相关代码见附件!


控制器层、持久层都有了一些介绍,剩下的就是业务层了! 
业务层中的关键问题在于事务控制!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-事务管理 
Xml代码   收藏代码
  1. <bean  
  2.     id="transactionManager"  
  3.     class="org.springframework.jdbc.datasource.DataSourceTransactionManager"  
  4.     p:dataSource-ref="dataSource" />  
  5. <tx:annotation-driven  
  6.     transaction-manager="transactionManager" />  

细化一下AccountService接口方法 
AccountService.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-1-23 
  3.  */  
  4. package org.zlex.spring.service;  
  5.   
  6. import org.springframework.dao.DataAccessException;  
  7. import org.springframework.transaction.annotation.Transactional;  
  8. import org.zlex.spring.domain.Account;  
  9.   
  10. /** 
  11.  * 账户业务接口 
  12.  *  
  13.  * @author <a href="mailto:[email protected]">梁栋</a> 
  14.  * @version 1.0 
  15.  * @since 1.0 
  16.  */  
  17. public interface AccountService {  
  18.   
  19.     /** 
  20.      * 获得账户 
  21.      *  
  22.      * @param username 
  23.      * @param password 
  24.      * @return 
  25.      */  
  26.     @Transactional(readOnly = true)  
  27.     Account read(String username, String password);  
  28.   
  29.     /** 
  30.      * 获得账户 
  31.      *  
  32.      * @param id 
  33.      * @return 
  34.      */  
  35.     @Transactional(readOnly = true)  
  36.     Account read(int id);  
  37.   
  38.     /** 
  39.      * 注册用户 
  40.      *  
  41.      * @param account 
  42.      * @return 
  43.      */  
  44.     @Transactional(readOnly = false, rollbackFor = DataAccessException.class)  
  45.     Account register(Account account);  
  46. }  

这里我把注解 @Transactional 调整到了具体的方法上,也就是说这样写的话,凡是加入注解的标注的方法都属于事务配置! 
Account register(Account account); 用做用户注册作用!  
@Transactional(readOnly = true) 只读属性 
@Transactional(readOnly = false, rollbackFor = DataAccessException.class) 只读关闭,遇到DataAccessException异常回滚!如果不对异常进行处理,该异常将一直向上层抛出,直至抛出到页面! 
如果你的Eclipse集成了SpringIDE,你可以观察一下这时的xml配置文件和AccoutServiceImpl.java的变化!  
Spring 注解学习手札_第8张图片  
 
这次,来个用户注册功能演示,故意在某个位置制造一个异常,看看是否正常回滚!  
先看注册控制器 
RegisterController.java 
Java代码   收藏代码
  1. /** 
  2.  * 2010-2-4 
  3.  */  
  4. package org.zlex.spring.controller;  
  5.   
  6. import java.text.DateFormat;  
  7. import java.text.SimpleDateFormat;  
  8. import java.util.Date;  
  9.   
  10. import org.springframework.beans.factory.annotation.Autowired;  
  11. import org.springframework.beans.propertyeditors.CustomDateEditor;  
  12. import org.springframework.stereotype.Controller;  
  13. import org.springframework.ui.ModelMap;  
  14. import org.springframework.web.bind.WebDataBinder;  
  15. import org.springframework.web.bind.annotation.InitBinder;  
  16. import org.springframework.web.bind.annotation.ModelAttribute;  
  17. import org.springframework.web.bind.annotation.RequestMapping;  
  18. import org.springframework.web.bind.annotation.RequestMethod;  
  19. import org.zlex.spring.domain.Account;  
  20. import org.zlex.spring.service.AccountService;  
  21.   
  22. /** 
  23.  * 用户注册控制器 
  24.  *  
  25.  * @author <a href="mailto:[email protected]">梁栋</a> 
  26.  * @version 1.0 
  27.  * @since 1.0 
  28.  */  
  29. @Controller  
  30. @RequestMapping(value = "/register.do")  
  31. public class RegisterController {  
  32.   
  33.     @Autowired  
  34.     private AccountService accountService;  
  35.   
  36.     @InitBinder  
  37.     public void initBinder(WebDataBinder binder) {  
  38.         // 忽略字段绑定异常  
  39.         // binder.setIgnoreInvalidFields(true);  
  40.   
  41.         DateFormat format = new SimpleDateFormat("yyyy-MM-dd");  
  42.         binder.registerCustomEditor(Date.class"birthday",  
  43.                 new CustomDateEditor(format, true));  
  44.     }  
  45.   
  46.     @RequestMapping(method = RequestMethod.GET)  
  47.     public String initForm(ModelMap model) {  
  48.         Account account = new Account();  
  49.         model.addAttribute("account", account);  
  50.         // 直接跳转到登录页面  
  51.         return "account/register";  
  52.     }  
  53.   
  54.     @RequestMapping(method = RequestMethod.POST)  
  55.     protected String submit(@ModelAttribute("account") Account account) {  
  56.         int id = accountService.register(account).getId();  
  57.         // 跳转到用户信息页面  
  58.         return "redirect:profile.do?id=" + id;  
  59.     }  
  60. }  

@InitBinder 用于表单自定义属性绑定。这里我们要求输入一个日期格式的生日。 
@RequestMapping(method = RequestMethod.GET) 用于初始化页面。 
@RequestMapping(method = RequestMethod.POST) 用于提交页面。 
再看注册页面 
register.jsp 
Jsp代码   收藏代码
  1. <html>  
  2. <head>  
  3. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
  4. <title>注册</title>  
  5. <link rel="stylesheet" type="text/css" href="css/style.css" />  
  6. <script type="text/javascript" src="js/calendar.js"></script>  
  7. </head>  
  8. <body>  
  9. <fieldset><legend>用户注册</legend><form:form  
  10.     commandName="account">  
  11.     <ul>  
  12.         <li><form:label path="username">用户名:</form:label><form:input  
  13.             path="username" /></li>  
  14.         <li><form:label path="password">密码:</form:label><form:password  
  15.             path="password" /></li>  
  16.         <li><form:label path="birthday">生日:</form:label><form:input  
  17.             path="birthday" onfocus="showDate(this);" /></li>  
  18.         <li><form:label path="email">Email:</form:label><form:input  
  19.             path="email" /></li>  
  20.         <li>  
  21.         <button type="submit">注册</button>  
  22.         <button type="reset">重置</button>  
  23.         </li>  
  24.     </ul>  
  25. </form:form></fieldset>  
  26. </body>  
  27. </html>  

这里我用了一个JavaScript日期控制标签: 
Html代码   收藏代码
  1. <script type="text/javascript" src="js/calendar.js"></script>  

使用起来就像是这样: 
Spring 注解学习手札_第9张图片  
非常好用!!!  当然,你完全可以使用JE上的那个JS控件! 
接下来稍微调整一下AccountService接口及其实现AccountServiceImpl 
AccountService.java 
Java代码   收藏代码
  1. public interface AccountService {  
  2.     // 省略  
  3.     /** 
  4.      * 注册用户 
  5.      *  
  6.      * @param account 
  7.      * @return 
  8.      */  
  9.     @Transactional(readOnly = false, rollbackFor = DataAccessException.class)  
  10.     Account register(Account account);  
  11.     // 省略  
  12. }  


Java代码   收藏代码
  1. @Service  
  2. public class AccountServiceImpl implements AccountService {  
  3.   
  4.     @Autowired  
  5.     private AccountDao accountDao;  

你可能感兴趣的:(Spring 注解学习手札)