您好,登錄后才能下訂單哦!
學習Spring這些技術性框架,光掌握理論知識是遠遠不夠了,我們要懂得學以致用,用鍵盤將學到的敲出來,在正確與錯誤中尋找Spring的用法。
為了給讀者一個直觀的概念,這里我用Spring搭建一個簡單的登錄,可以讓你很快的了解Spring在持久層、業務層、表現層是怎么運作的,這樣后面我們分模塊講解的時候,讀者也能很快的知道。
本文所用工具為Eclipse IDE,數據庫為Oracle 11g。
首先我們來了解登錄這個功能,用戶訪問登錄頁面,輸入賬號和密碼,點擊登錄,后臺驗證是否有賬號和密碼匹配,如果匹配成功,則跳到歡迎頁面,并顯示用戶積分,如果登錄失敗則返回登錄頁面,并提示登錄失敗信息。
簡化圖:
有經驗的程序員知道在開發之前都會先寫開發設計或者畫類圖,這里面筆者也從開發設計開始,針對登錄的全過程先設計好再開發,畢竟磨刀不誤砍材功。
上面是登錄功能設計的全部過程大致描述一下:
1.首先訪問login.jsp,返回可輸入用戶賬號以及密碼的表單登錄頁面
2.用戶在登錄頁面輸入賬號、密碼,提交表單到服務器,Spring根據配置調用LoginController控制器響應請求。
3.LoginController調用LoginService/isExistUser方法,根據傳遞的用戶、密碼參數查詢是否存在用戶,LoginService內部通過持久層LoginDao與數據進行交互。
4.如果匹配不到用戶,則返回Login.jsp,并返回提示信息,如果匹配成功,則進入下一步。
5.LoginController調用LoginService/findUserByUsername獲取當前登錄人的實體,設置當前登錄時間以及積分更新后的值。
6.LoginController調用LoginService/loginsuccess()方法,進行登錄成功后的業務處理,首先更新用戶信息,以及創建LoginInfo日志對象并插入數據庫。
7.重定向歡迎頁面welcome.jsp頁面,并返回響應。
環境準備:
1.表創建
create table TB_USER --用戶表 ( userid VARCHAR2(20), username VARCHAR2(20), credits NUMBER, password VARCHAR2(100), last_login_time DATE, last_login_ip VARCHAR2(30) ) create table TB_LOGIN_LOG --用戶登錄日志 ( login_id VARCHAR2(20), login_user_id VARCHAR2(20), ip VARCHAR2(20), login_time DATE ) --插入一條用戶 insert into tb_user (USERID, USERNAME, CREDITS, PASSWORD, LAST_LOGIN_TIME, LAST_LOGIN_IP) values ('zhangsan', '張三', 0, '123456', to_date('06-07-2016 09:10:39', 'dd-mm-yyyy hh34:mi:ss'), '0:0:0:0:0:0:0:1');
2.建立工程
良好的包分類以及分層會給維護以及其他開發帶來的很大的好處,一般企業項目中包的分類還應該按項目模塊分,本文的項目較簡單,因此分類就簡單化,同時對于Spring 的配置文件一般都會有文件夾存放,這里我們就不詳細說明,相信讀者在工作中看到的項目大多數都是結構良好的。
關于Spring3.0相關jar,這里面讀者可以自己去下載。
3.創建實體類
我們可以認為實體類是貫穿業務層、持久層、表現層的一條線,一個功能模塊往往是圍繞一個實體類,業務層修改實體類狀態,持久層將實體類存儲到數據庫,表現層展現實體類的狀態,可見實體類的重要性。
public class User { private String userid;//用戶ID private String username;//用戶姓名 private Integer credits;//積分 private String password;//密碼 private Date lastlogintime;//最后登錄時間 private String loginip;//最后登錄ip } public class LoginInfo { private String loginid;//登錄主鍵 private String loginuserid;//登錄人ID private String loginip;//登錄IP private Date logintime;//登錄時間 } ......省略get、set方法
4.持久層LoginDao與LoginLogDao
我們先看看關于操作User的DAO,主要包括3個方法
1.getUserCount(),返回結果為×××,如果返回1,則表示用戶、密碼匹配正確,返回0則表示用戶、密碼匹配錯誤,實際中這里存在密碼加密解密功能,這里我們簡單化。
2.findUserByUsername(),根據用戶賬號獲取User實體。
3.updateUserInfo(),更新用戶最后登錄時間、 積分以及用戶IP.
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.RowCallbackHandler; import org.springframework.stereotype.Repository; import domain.User; @Repository //通過Spring注解定義一個Dao public class LoginDao { @Autowired//自動注入id為JdbcTemplate的bean private JdbcTemplate jdbcTemplate; /** * * @author zangyn * @date 2016-8-19 下午1:54:16 * @Title: getUserCount * @Description: 根據賬號、密碼匹配是否存在 * @return int 返回類型 * @throws */ public int getUserCount(String username,String password){ String sql="SELECT COUNT(*) FROM TB_USER U WHERE U.USERID=? AND U.PASSWORD=?"; int count=jdbcTemplate.queryForInt(sql,new Object[]{username,password}); return count; } /** * * @author zangyn * @date 2016-8-19 下午1:54:43 * @Title: findUserByUsername * @Description: 根據Userid查詢是否存在該User * @return User 返回類型 * @throws */ public User findUserByUsername(String userid){ String sql="SELECT * FROM TB_USER U WHERE U.USERID=?"; final User u=new User(); jdbcTemplate.query(sql, new Object[]{userid}, new RowCallbackHandler() { @Override public void proce***ow(ResultSet rs) throws SQLException { u.setUserid(rs.getString("userid")); u.setUsername(rs.getString("username")); u.setCredits(rs.getInt("credits")); } }); return u; } /** * * @author zangyn * @date 2016-8-19 下午1:55:28 * @Title: updateUserInfo * @Description: 更新用戶相關信息 * @return void 返回類型 * @throws */ public void updateUserInfo(User user){ String sql="UPDATE TB_USER U SET U.LAST_LOGIN_TIME=?,U.LAST_LOGIN_IP=?,U.CREDITS=? WHERE U.USERID=?"; jdbcTemplate.update(sql, new Object[]{user.getLastlogintime(),user.getLoginip(),user.getCredits(),user.getUserid()}); } }
Spring2.5以后,可以使用注解的方式定義Bean,相比較XML配置,注解配置簡單太多,所以我們盡量要以注解為主進行Bean配置。
這里我們用@Repository定義了Dao bean的配置,使用 @Autowired將容器的Bean注入進來。
從上面可以看到關于數據庫操作,我們引入了Spring JdbcTemplate,用過原始JDBC的都知道,在執行數據庫操作之前都需要經過:獲取連接>創建Statement>執行數據庫操作>獲取結果>關閉Statement>關閉結果集>關閉連接,同時我們還要關注異常的處理,每次執行sql,我們的1/3代碼都重復的寫以上的代碼,對于以上樣板式的代碼,Spring JDBC進行了薄層的封裝,將樣板式的代碼進行了封裝,用戶只需要編寫那些必不可少的代碼。
關于拼寫SQL注意:在拼寫較長的sql時,我們經常需要換行,由于上下行最終會轉換為一個sql,這時候如果在每行的末尾沒有空格,就會存在連在一起的情況,因此,良好的拼寫SQL方法是在每行開始和結尾都打一個空格。
關于JdbcTemplate各種方法運用,我會在后面一篇詳細介紹。
LoginLogDao只有一個存放日志記錄的方法,代碼如下:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import domain.LoginInfo; @Repository //通過Spring注解定義一個Dao public class LoginLogDao { @Autowired//自動注入id為JdbcTemplate的bean private JdbcTemplate jdbcTemplate; public void insertLoginfo(LoginInfo info){ String sql="INSERT INTO TB_LOGIN_INFO(LOGIN_ID,LOGIN_USER_ID,IP,LOGIN_TIME) VALUES(login_info_seq.nextval,?,?,?)"; Object[] obj=new Object[]{info.getLoginuserid(),info.getLoginip(),info.getLogintime()}; jdbcTemplate.update(sql, obj); } }
5.數據源配置以及Spring 加載Dao
在src下創建一個名為applicationContext.xml的文件,配置文件內容如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- ①自動掃描com.gloryscience.dao包下的所有類,將帶有注解的類 納入spring容器管理--> <context:component-scan base-package="dao"></context:component-scan> <!-- ②定義一個使用DBCP實現的數據源--> <bean id="dataSourceDBCP" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/> <property name="username" value="gloryseience"/> <property name="password" value="gloryseience"/> </bean> <!-- ③定義Jdbc模板bean--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSourceDBCP"></property> </bean> </beans>
Spring的<context:component-scan>掃描指定包下的類,這樣@Repository、@Autowired注解才能起作用。
關于數據源配置這里只存在不同的配置源問題,關于為什么要這么配置,只能說是JDBC規范要求。
jdbcTemplate這個bean將dataSource注入到JdbcTemplate,然后JdbcTemplate Bean通過@Autowired注入到LoginLogDao和LoginDao中。
6. 服務層 LoginService
loginservice接口有三個方法:isExistUser用于驗證賬號、密碼正確性、findUserByUsername用于根據用戶賬號加載User實體、loginSuccess用于登錄成功進行積分變更、日志插入操作,這里存在事務操作,更新tb_user并插入tb_login_info,但是我們卻沒看到關于事務控制的代碼,Spring的一大優點就是將事務從代碼中解放出來,下面我會講解關于Spring處理事務能力。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import dao.LoginDao; import dao.LoginLogDao; import domain.LoginInfo; import domain.User; @Service //將LoginServcie標注為一個服務層Bean public class LoginService { @Autowired private LoginDao logindao; @Autowired private LoginLogDao loginlogdao; public boolean isExistUser(String userid,String password){ int count=logindao.getUserCount(userid, password); return count>0; } public User findUserByUsername(String userid){ return logindao.findUserByUsername(userid); } public void loginSuccess(User u){ u.setCredits(u.getCredits()+5); LoginInfo info=new LoginInfo(); info.setLoginuserid(u.getUserid()); info.setLogintime(u.getLastlogintime()); info.setLoginip(u.getLoginip()); loginlogdao.insertLoginfo(info); logindao.updateUserInfo(u); } }
7.Spring加載Service
Spring的事務雖然不用出現在代碼中,但是我們需要告訴Spring哪些業務類需要工作于事務環境以及事務的規則等內容,讓Spring根據這些信息自動為目標業務類添加事務管理功能。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" 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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!-- 自動掃描com.gloryscience.dao包下的所有類,將帶有注解的類 納入spring容器管理--> <context:component-scan base-package="dao"></context:component-scan> <context:component-scan base-package="service"></context:component-scan> <bean id="dataSourceDBCP" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/> <property name="username" value="gloryseience"/> <property name="password" value="gloryseience"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSourceDBCP"></property> </bean> <!-- 配置事務管理器聲--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSourceDBCP"></property> </bean> <!-- 通過AOP配置提供事務增強,讓Service包下所有方法擁有事務 --> <aop:config> <aop:pointcut id="serviceMethod" expression="execution(* service..*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> </beans>
關于業務層和配置已經完成,我們先單元測試一下。
8.單元測試
Spring 3.0的測試框架可以和Junit4整合,通過@RunWith可以指定SpringJUnit4Cla***unner測試運行器,該運行器是Spring提供的,可以將Spring容器和Junit4測試框架結合。@ContextConfiguration也是Spring提供的注解,用于指定Spring配置文件。@Test用于將方法標注為測試方法,
import static org.junit.Assert.*; import java.util.Date; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4Cla***unner; import domain.User; import service.LoginService; @RunWith(SpringJUnit4Cla***unner.class)//基于JUnit4的Spring測試框架 @ContextConfiguration(locations={"classpath:applicationContext.xml"})//啟動spring容器 public class TestUserService { @Autowired//注入spring容器bean private LoginService loginservice; @Test public void isExistUser() { boolean a1=loginservice.isExistUser("zhangsan", "123456"); boolean a2=loginservice.isExistUser("zhangsan", "1"); assertTrue(a1); assertTrue(!a2); } @Test public void findUserbyUsername() { User u=loginservice.findUserByUsername("zhangsan"); assertEquals(u.getUserid(), "zhangsan"); } @Test public void loginSuccess(){ User u=new User(); u.setUserid("zhangsan"); u.setCredits(0); u.setLoginip("127.0.0.1"); u.setLastlogintime(new Date()); loginservice.loginSuccess(u); } }
9.配置SpringMVC
業務層以及持久層已經完成,現在我們需要用SpringMVC將展現層加入個整個功能里。首先對web.xml進行配置
<?xml version="1.0" encoding="UTF-8"?> <web-app id="acms" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <!-- 從類文件下加載spring配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- 啟動spring 的監聽器,引用contextConfigLocation的配置文件 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</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>classpath:Spring-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Spring</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </web-app>
web容器通過上下文參數指定Spring配置文件,多個配置文件可用逗號分開,然后web容器在啟動的時候會自動啟動監聽器ContextLoaderListener,該監聽器的作用是獲取Spring配置文件并啟動Spring容器,注意將log4j.properties放在類路徑下,以便日志引擎生效。
SpringMVC也是通過截獲URL請求,然后進行相關處理。
這里面注意SpringMVC也有個配置文件,該配置文件的文件與這里定義的Servlet名有一個契約,即
采用<servlet名>-servlet.xml,在這里Servlet的名稱為Spring,意思是/WEB-INF下必須有個Spring-servlet.xml的配置文件,這個配置文件無需通過web.xml的上下文進行配置,SpringMVC的Servlet會自動將Spring-servlet.xml與其他配置文件拼裝。
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:Spring-servlet.xml</param-value>
</init-param>
這里如果配置這個參數則配置文件就不會去執行上面的契約去查找Spring-servlet,會根據我們自己配置的文件查找。
Servlet的URL路徑進行定義我們讓所有后綴以.html的URL都能被Spring Servlet截獲,然后轉給SpringMVC進行處理,請求被SpringMVC接受后,首先根據URL找到目標的處理控制器,并將請求參數封裝成一個“命令”對象一起傳給控制器處理,控制器調用Spring 的業務Bean進行處理并返回視圖。
10.控制器類LoginController
LoginController負責處理登錄請求,完成登錄業務,根據登錄結果返回不同頁面。
import java.util.Date; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import domain.LoginCommand; import domain.User; import service.LoginService; @Controller public class LoginController { @Autowired private LoginService loginservice; @RequestMapping(value="/index.html") public String loginPage(){ return "login"; } @RequestMapping(value="/loginCheck.html") public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){ boolean isValid=loginservice.isExistUser(loginCommand.getUserid(), loginCommand.getPassword()); if(!isValid){ return new ModelAndView("login","error","用戶或密碼錯誤"); }else{ User user=loginservice.findUserByUsername(loginCommand.getUserid()); user.setLoginip(request.getRemoteAddr()); user.setLastlogintime(new Date()); request.getSession().setAttribute("user", user); loginservice.loginSuccess(user); return new ModelAndView("welcome"); } } }
@Controller將LoginController標注為SpringMVC控制器,處理HTTP請求,一個控制器包含多個HTTP請求路徑的處理方法,通過@RequestMapping指定方法映射請求路徑。
請求參數會根據參數名稱默認契約自動綁定到響應方法的入參中,在loginCheck(HttpServletRequest request,LoginCommand loginCommand),請求參數會根據名稱匹配綁定到loginCommand中。
public class LoginCommand { private String userid;//用戶id private String password;//用戶密碼 省略get/set }
請求方法可以返回ModelAndView或者字符串,SpringMVC會解析并自動轉向目標響應頁面。關于ModelAndView讀者只需知道它目前代表一種視圖就行,后面會解釋。
現在需要知道ModelAndView返回的兩種形式,ModelAndView("login","error","用戶或密碼錯誤")和ModelAndView("main"),第一個參數代表視圖的邏輯名,第二個以及第三個代表數據模型名稱和數據模型對象,數據模型對象將以數據模型名稱為參數名放置到request屬性中。
SpringMVC的配置文件Spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" 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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <!-- 掃描controllerd,應用spring注釋 --> <context:component-scan base-package="controller" /> <!-- 對模型視圖解析 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/login/" p:suffix=".jsp" /> </beans>
上面SpringMVC通過配置來解析ModelAndView,Spring有許多解析ModelAndView的方式,這里我們用InternalResourceViewResolver,它通過添加前后綴方式來解析。如ModelAndView("welcome"),將解析為/WEB-INF/login/welcome.jsp
11.JSP頁面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!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>論壇登錄</title> </head> <c:if test="${not empty error}"> <font color="red"><c:out value="${error}"></c:out></font> </c:if> <form action="<c:url value='/loginCheck.html'/>" method="post"> <input type="text" id="userid" name="userid" placeholder="用戶名" maxlength="40"/> <input type="password"id="password" name="password" placeholder="密碼" maxlength="40"/> <input type="submit" value="登 錄" /> <input type="reset" value="重置" /> </form> </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!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>論壇主頁</title> </head> ${user.username},歡迎你進入本論壇,你的積分為${user.credits}. </body> </html>
這里我引入c標簽,需要引入兩個jar包。
剩下的可以直接部署了,然后訪問http://localhost:8088/項目部署名稱/,輸入zhangsan、123456即可登錄成功。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。