一、深入淺出Struts2
- 什么是Struts2?
struts2是一種基於MVC的輕量級的WEB應用框架。有了這個框架我們就可以在這個框架的基礎上做起,這樣就大大的提高了我們的開發效率和質量,為公司節省了不少的人力成本。
- 為什么使用Struts2?
struts2實現了視圖與業務應用邏輯的解耦(軟件工程的高內聚,低耦合原則)。
在Model 1時代,開發web項目我們都是在JSP里寫入處理業務邏輯的JAVA代碼,尤其像涉及到數據庫和頁面form表單數據之間交互的時候,每次都要寫連接、查詢、增加、修改、刪除等數據庫代碼。有各種如HTML與Java,JavaScript在一個頁面的,可讀性差,重復,多余,耦合,性能差,不易維護等各種問題。讓我們觀察model1與model2的不同:

model 1的程序流程
到Model2時代,JSP不再承擔控制器的責任。它僅僅是表現層角色,僅僅用於將結果呈現給客戶,JSP頁面的請求與Servlet(控制器)交互,而Servlet負責與后台 的Javabean通信。在Model2模式下,模型(Model)由JavaBean充當,視圖(View)由JSP頁面充當,而控制器(Controller)則由Servlet充當,Model2模式也叫“MVC模式”。

model 2 程序流程
在后來開發者根據Model2模式開發出了一個框架,那就是我們現在用的Struts2。控制器(Contoller)由FilterDispather充當,模型(Model)由Action充當,視圖(View)由Result來充當,這樣實現了與Servlet API的解耦,使得ACtion的單元測試更加簡單,而且強大的類型轉換也使得我們少做了很多重復的工作。下圖是Struts2 MVC結構:

Struts2的請求周期:
1、客戶端初始化一個指向Servlet容器(例如Tomcat)的請求
2、這個請求經過一系列的過濾器(Filter)(這些過濾器中有一個叫做ActionContextCleanUp的可選過濾器,這個過濾器對於Struts2和其他框架的集成很有幫助,例如:SiteMesh Plugin)
3、接着FilterDispatcher被調用,FilterDispatcher詢問ActionMapper來決定這個請是否需要調用某個Action
FilterDispatcher是控制器的核心,就是mvc中c控制層的核心。下面粗略的分析下我理解的FilterDispatcher工作流程和原理:FilterDispatcher進行初始化並啟用核心doFilter
4、如果ActionMapper決定需要調用某個Action,FilterDispatcher把請求的處理交給ActionProxy
5、ActionProxy通過ConfigurationManager詢問框架的配置文件,找到需要調用的Action類 ,這里,我們一般是從struts.xml配置中讀取。
6、ActionProxy創建一個ActionInvocation的實例。
7、ActionInvocation實例使用命名模式來調用,在調用Action的過程前后,涉及到相關攔截器(Intercepter)的調用。
- Struts2的兩種配置方式
配置就像程序的影子,與程序總是與影隨行。大部分框架技術,配置總是作為其中重要的組成部分,在框架的運行中發揮作用。聲明應用程序有兩種不同的方式,XML和注解的方式。
XML配置方式:
struts.xml的配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd">
<!-- 上面的頭注意版本,從樣例里復制過來 -->
<struts>
<constant name="struts.objectFactory" value="spring"></constant>
<package name="mypackage" extends="struts-default">
<action name="myNews6Action_*" class="myNews6Action" method="{1}">
<result name="success">/WEB-INF/jsp/index.jsp</result>
<result name="deleteok">/WEB-INF/jsp/ok.jsp</result>
<result name="deleteon">/WEB-INF/jsp/error.jsp</result>
</action>
</package>
</struts>
Action類只要繼承ActionSupport類:
package myNews6.action; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Controller; import com.opensymphony.xwork2.ActionSupport; import myNews6.entity.News; import myNews6.service.MyNews6Service; @Controller @Scope("prototype") public class MyNews6Action extends ActionSupport { @Autowired private MyNews6Service mns; private List<News> list; private Integer id; public List<News> getList() { return list; } public void setId(Integer id) { this.id = id; } public String getAllNews(){ list=mns.getAllNews(); return "success"; } public String deleteNewsById(){ String value=mns.deleteNewsById(id); return value; } }
注解配置方式:
要使用注解方式必須添加一個額外包:struts2-convention-plugin-2.x.x.jar。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN" "http://struts.apache.org/dtds/struts-2.1.7.dtd"> <struts> <!-- 請求參數的編碼方式--> <constant name="struts.i18n.encoding" value="UTF-8"/> <!-- 指定被struts2處理的請求后綴類型。多個用逗號隔開--> <constant name="struts.action.extension" value="action,do,htm"/> <!-- 當struts.xml改動后,是否重新加載。默認值為false(生產環境下使用),開發階段最好打開 --> <constant name="struts.configuration.xml.reload" value="true"/> <!-- 是否使用struts的開發模式。開發模式會有更多的調試信息。默認值為false(生產環境下使用),開發階段最好打開 --> <constant name="struts.devMode" value="false"/> <!-- 設置瀏覽器是否緩存靜態內容。默認值為true(生產環境下使用),開發階段最好關閉 --> <constant name="struts.serve.static.browserCache" value="false" /> <!-- 指定由spring負責action對象的創建 <constant name="struts.objectFactory" value="spring" /> --> <!-- 是否開啟動態方法調用--> <constant name="struts.enable.DynamicMethodInvocation" value="false"/> </struts>
action類的注解:
/**
* Struts2基於注解的Action配置
*
*/
@ParentPackage("struts-default") @Namespace("/annotation_test") @Results( { @Result(name = "success", location = "/main.jsp"), @Result(name = "error", location = "/error.jsp") }) @ExceptionMappings( { @ExceptionMapping(exception = "java.lange.RuntimeException", result = "error") })
public class LoginAction extends ActionSupport {
private static final long serialVersionUID = 2730268055700929183L;
private String loginName;
private String password;
@Action("login") //或者寫成 @Action(value = "login")
public String login() throws Exception {
if ("yjd".equals(loginName) && "yjd".equals(password)) {
return SUCCESS;
} else {
return ERROR;
}
}
@Action(value = "add", results = { @Result(name = "success", location = "/index.jsp") })
public String add() throws Exception {
return SUCCESS;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password= password;
}
}
這樣就完成了一個基於注解的action配置。
Struts2的兩種配置都可以達到同樣的效果,只不過xml是把配置外部,annotation是把配置內部化,他們各自的優缺點、適用場合應該是很明顯的。 如果一個配置只是用來描述某個組件本身的屬性,那么annotation優先,比如hibernate、validate;優點是配置和組件往往需要同時修改,放在一起便於維護、保證一致性;反之,如果配置是描述一堆組件如何協作、關聯,或者很難說清是針對哪個組件的,那么應該用xml,比如spring配置。優點是配置集中、架構清晰、維護配置不需修改組件代碼,反之亦然。
- SpringMVC替換Struts2

springMVC的請求周期:
1、客戶端發出一個http請求給web服務器,web服務器對http請求進行解析,如果匹配DispatcherServlet的請求映射路徑(在web.xml中指定),web容器將請求轉交給DispatcherServlet.
2、DipatcherServlet接收到這個請求之后將根據請求的信息(包括URL、Http方法、請求報文頭和請求參數Cookie等)以及HandlerMapping的配置找到處理請求的處理器(Handler)。
3-4、DispatcherServlet根據HandlerMapping找到對應的Handler,將處理權交給Handler(Handler將具體的處理進行封裝),再由具體的HandlerAdapter對Handler進行具體的調用。
5、Handler對數據處理完成以后將返回一個ModelAndView()對象給DispatcherServlet。
6、Handler返回的ModelAndView()只是一個邏輯視圖並不是一個正式的視圖,DispatcherSevlet通過ViewResolver將邏輯視圖轉化為真正的視圖View。
7、Dispatcher通過model解析出ModelAndView()中的參數進行解析最終展現出完整的view並返回給客戶端
springMVC搭建的例子:
1. jar包引入
Spring 2.5.6:spring.jar、spring-webmvc.jar、commons-logging.jar、cglib-nodep-2.1_3.jar等;
1、配置web.xml
指除了Control層外的其它Bean的Spring配置文件,定義DispatcherServlet。這里是把spring與spring mvc的功能隨着服務器啟動而啟動;
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- 除了Control層外的其它Bean的Spring容器設置,這個與SSH整合的時候一樣 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 配置DispatcherServlet --> <!-- 名字為mvc,那么我們在WEB-INF中需要一個名為mvc-servlet.xml的spring mvc配置文件來對Control層的Bean、相關頁面以及Spring mvc提供的一些工具Bean進行管理 --> <servlet> <servlet-name>mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- 接收頁面以.abc結尾的請求 --> <servlet-mapping> <servlet-name>mvc</servlet-name> <url-pattern>*.abc</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
2、編寫處理請求的Controller(處理器)
package cn.framelife.mvc.control;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import cn.framelife.mvc.entity.User;
/**
* @Controller 通過Spring的自動掃描功能,把一個POJO轉化為處理請求的控制器
*通過@RequestMapping標注,可以將特定的url和具體的controller類或controller類中的方法綁定。
* @RequestMapping("/user") 處理來自/user的請求,一般來說是用於區分不同模塊的
*/
@Controller
@RequestMapping("/user")
public class UserControl {
/**
* 處理/user的請求,請求的方法為POST
* 參數的User對象是把頁面的表單值放進一個User對象中
* ModelAndView 是返回一個View。在這里我們是一個JSP頁面。
*/
@RequestMapping(method=RequestMethod.POST)
public ModelAndView createUser(User user){
System.out.println(user.getUsername()+"-"+user.getPassword());
//ModelAndView.setViewName("/success") 根據mvc的配置文件,可知返回的是/user/success.jsp
ModelAndView view = new ModelAndView();
view.setViewName("/success");
//把一個user對象放到ModelAndView中
user.setUsername("li");
view.addObject(user);
return view;
}
}
3、編寫視圖文件
我們在WebRoot/uesr目錄下有一個add.jsp及一個success.jsp頁面。add.jsp是用以表單輸入。而success.jsp是Controller處理完后返回的頁面。
add.jsp:
<body> <form action="user.abc" method="post"> 用戶名:<input type="text" name="username"><br/> 密 碼:<input type="text" name="password"><br/> <input type="submit"> </form> </body> success.jsp: <body> success!!${user.username}. </body>
4、配置Spring MVC的配置文件,使控制器、視圖解析器等生效
mvc-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: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/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"> <!-- 掃描控制器 --> <context:component-scan base-package="cn.framelife.mvc.control"></context:component-scan> <!-- 視圖名稱解析器 --> <!-- Controller中:ModelAndView.setViewName("/success") 配置的是/user/success.jsp --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/user" p:suffix=".jsp"></bean> </beans>
5、配置其它的Bean
諸如service層、dao層等Bean在applicationContext.xml中配置,如我們之前做的工作一樣。
1、Struts2是類級別的攔截, 一個類對應一個request上下文,SpringMVC是方法級別的攔截,一個方法對應一個request上下文,而方法同時又跟一個url對應,所以說從架構本身上SpringMVC就容易實現restful url,而struts2的架構實現起來要費勁,因為Struts2中Action的一個方法可以對應一個url,而其類屬性卻被所有方法共享,這也就無法用注解或其他方式標識其所屬方法了。
2、由上邊原因,SpringMVC的方法之間基本上獨立的,獨享request response數據,請求數據通過參數獲取,處理結果通過ModelMap交回給框架,方法之間不共享變量,而Struts2搞的就比較亂,雖然方法之間也是獨立的,但其所有Action變量是共享的,這不會影響程序運行,卻給我們編碼 讀程序時帶來麻煩,每次來了請求就創建一個Action,一個Action對象對應一個request上下文。
3、由於Struts2需要針對每個request進行封裝,把request,session等servlet生命周期的變量封裝成一個一個Map,供給每個Action使用,並保證線程安全,所以在原則上,是比較耗費內存的。
4、 攔截器實現機制上,Struts2有以自己的interceptor機制,SpringMVC用的是獨立的AOP方式,這樣導致Struts2的配置文件量還是比SpringMVC大。
5、SpringMVC的入口是servlet,而Struts2是filter(這里要指出,filter和servlet是不同的。以前認為filter是servlet的一種特殊),這就導致了二者的機制不同,這里就牽涉到servlet和filter的區別了。
6、SpringMVC集成了Ajax,使用非常方便,只需一個注解@ResponseBody就可以實現,然后直接返回響應文本即可,而Struts2攔截器集成了Ajax,在Action中處理時一般必須安裝插件或者自己寫代碼集成進去,使用起來也相對不方便。
7、SpringMVC驗證支持JSR303,處理起來相對更加靈活方便,而Struts2驗證比較繁瑣,感覺太煩亂。
8、spring MVC和Spring是無縫的。從這個項目的管理和安全上也比Struts2高(當然Struts2也可以通過不同的目錄結構和相關配置做到SpringMVC一樣的效果,但是需要xml配置的地方不少)。
9、 設計思想上,Struts2更加符合OOP的編程思想, SpringMVC就比較謹慎,在servlet上擴展。
10、SpringMVC開發效率和性能高於Struts2。
11、SpringMVC可以認為已經100%零配置。
二、深入淺出Spring
- Spring IOC容器
在Spring IOC容器中,有兩個主要的容器系列:BeanFactory和ApplicationContext;BeanFactory它提供了最基本的IOC容器的功能,實例化對象、配置對象之間的依賴關系,在spring中,所有的對象都是由BeanFactory工廠來生廠管理的。ApplicationContext則提供了更多的面向企業級應用的功能,它是BeanFactory的子接口,擁有BeanFactory的所有功能;並提供了文本信息解析工具,提供了載入文件資源的通用方法,可以向注測為監聽器的Bean發送事件。
- spring依賴反轉模式的理解
什么的依賴反轉,打個比方:我每次吃飯的時候都要買一雙一次性筷子(每一次使用都要new一次),在這樣的關系下,是”我“(即調用者)每次都要”主動“去買一次性筷子(另一個類),我對筷子說你老老實實的過來我的手上,是我控制了筷子,那好,在這種控制正轉的關系下,放在現實生活當中,肯定是不現實的,而且人是懶惰的,他總會去創造出更加方便自己生活的想法,更確切的做法是,買一雙普通的筷子(非一次性),把他放在一個容器當中(在Spring中叫做IOC容器),你需要使用的時候就對容器說:IOC我想要用筷子(向容器發出請求),接着筷子就會”注入“到的手上,而在這個過程當中,你不再是控制方,反而演變成一名請求者(雖然本身還是調用者),依賴於容器給予你資源,控制權坐落到了容器身上,於是這就是人們俗稱的控制反轉。——再理解為調用者應該依賴被調用者,而不是依賴於它的具體實現。
如何來實現依賴反轉模式,我可以用依賴注入,依賴注入有3種方式,分別為構造注入,設置注入,接口注入。
-
接口注入:
在接口中定義要注入的信息,並通過接口來完成注入。(Spring不支持這種注入方式--不支持的原因是--Spring聲稱其是非入侵式的《離開這個框架也能活》,如果使用接口注入的話,就違背了這一原則),這里不做代碼實現講解。
public interface Computeable{
void inject(Printer p);
}
public class Computer implements Computeable{
Printer p;
public void inject(Printer p){
this.p=p;
}
}
2.構造器注入:
構造器注入方式,表示的是調用者在實例化的時候必須注入被調用者的實例,把上例改成構造器注入;
public class Computer {
Printer p;
public Computer(Printer p){
this.p=p;
}
}
Computer c=new Computer(new ColorPrinter());
3.setter注入:
setter注入方式,表示的是調用者通過設置一個全局屬性並給設置set方法來注入;
public interface UserDao{ addUser(String username); } public class UserDaoImpl implements UserDao{ @Override public void addUser(String username) { System.out.println("添加用戶:"+username); } } public class UserMessage{ private UserDaoImpl userDao; //使用設值方式賦值 public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void addUser(String userName, String password) { userDao.addUser(userName, password); } } public class test{ UserDao usedao =new UserDaoImpl(); UserMessage userMessage=new UserMessage(); userMessage.setUserDao(userdao); }
- ApplicationContext.xml文件的配置
spring配置文件是用於指導Spring工廠進行Bean生產、依賴關系注入(裝配)及Bean實例分發的"圖紙"。Java EE程序員必須學會並靈活應用這份"圖紙"准確地表達自己的"生產意圖"。Spring配置文件是一個或多個標准的XML文檔,applicationContext.xml是Spring的默認配置文件,當容器啟動時找不到指定的配置文檔時,將會嘗試加載這個默認的配置文件。
1.純XML配置的Spring
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd"> <!-- 類似於財務部門一樣,類就是錢,所有需要類的實例都由srping去管理 --> <bean id="myIndexAction" class="ssh.action.IndexAction" scope="prototype"> <!-- setIs(myIndexService) --> <property name="is" ref="myIndexService"/> </bean> <!-- myIndexService = new ssh.service.IndexServiceImpl() --> <bean id="myIndexService" class="ssh.service.IndexServiceImpl" scope="prototype"> <property name="id" ref="myIndexDao"/> </bean> <bean id="myIndexDao" class="ssh.dao.IndexDaoImpl" scope="prototype"> <!-- 晚點再注入能用的seesionFactory --> <property name="sessionFactory"><null/></property> </bean> </beans>
純的XML配置方式進一步降低了耦合,使得應用更加容易擴展,即使對配置文件進一步修改也不需要工程進行修改和重新編譯。
在處理大的業務量的時候,用XML配置應該更加好一些。因為XML更加清晰的表明了各個對象之間的關系,各個業務類之間的調用。同時spring的相關配置也能一目了然。
但它也有缺點:配置文件讀取和解析需要花費一定的時間,配置文件過多的時候難以管理,無法對配置的正確性進行校驗,增加了測試難度。
2.使用注解的spring配置文件
在配置文件中加上注解解析器
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd"> <!-- 注解解析器 --> <context:component-scan base-package="包名"/> </beans>
在各個JavaBean中加注解:
@Controller @Scope("prototype")
public class MyNews6Action extends ActionSupport {
@Autowired private MyNews6Service mns;
@Service @Scope("prototype")
public class MyNews6ServiceImpl implements MyNews6Service {
@Autowired private MyNews6Dao mnd;
@Repository @Scope("prototype")
public class MyNews6DaoImpl implements MyNews6Dao {
@Autowired
private SessionFactory sf;
注解也有自己的優缺點;
優點有:
1. 在class文件中,可以降低維護成本,annotation的配置機制很明顯簡單
2. 不需要第三方的解析工具,利用java反射技術就可以完成任務
3. 編輯期可以驗證正確性,差錯變得容易
4. 提高開發效率
缺點有:
1. 如果需要對於annotation進行修改,那么要重新編譯整個工程
2. 業務類之間的關系不如XML配置那樣容易把握。
3. 如果在程序中annotation比較多,直接影響代碼質量,對於代碼的簡潔度有一定的影響。
- Spring AOP的理解
AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程(也叫面向方面),可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP可以說也是這種目標的一種實現。spring有兩種代理機制,一種是JDK本身提供的動態代理,另一種是使用CGLIB的代理,JDK的代理只支持接口方式,而CGLIB則可以應用到任何類;
下圖展示了AOP的關鍵概念:

AOP就是根據代理模式,創建兩個實現同一個接口的類,一個代理類和一個目標類,目標類里創建核心業務邏輯代碼的方法,然后代理類通過調用目標類的方法來執行核心業務邏輯代碼,並在核心業務邏輯代碼的前后插入非核心業務邏輯代碼,來實現靜態代理。把核心業務邏輯代碼與非核心業務邏輯代碼分離開,並把非核心業務邏輯代碼用動態代理技術生成一個切面,每個核心業務邏輯就是一個連接點,每個核心業務邏輯的前后都可以看做是一個切入點,這就是AOP的思想。
下面來做個簡單的例子:
1.定義一個接口
package aop006; public interface Girl { public void KFC(String datetime); public void meet(String datetime); }
2.目標類都實現它
package aop006; /* * */ public class Girl1 implements Girl{ public void KFC(String datetime){ System.out.println("[核心業務邏輯]我是第一個女孩"); System.out.println("[核心業務邏輯]"+datetime+"吃肯德基"); } public void meet(String datetime){ System.out.println("[核心業務邏輯]我是第一個女孩"); System.out.println("[核心業務邏輯]"+datetime+"約會"); } }
package aop006; /* * */ public class Girl2 implements Girl { public void KFC(String datetime){ System.out.println("[核心業務邏輯]我是第二個女孩"); System.out.println("[核心業務邏輯]"+datetime+"吃肯德基"); } public void meet(String datetime){ System.out.println("[核心業務邏輯]我是第二個女孩"); System.out.println("[核心業務邏輯]"+datetime+"約會"); } }
3.代理類實現它,並實現動態代理
package aop006; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class GirlHandler implements InvocationHandler { private Object targer;// 核心類是不固定 //通過構造方法注入核心類 public GirlHandler(Object targer) { this.targer = targer; } /* * return 返回是原來核心類方法所返回的內容 method 就是要執行的方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub //前置代碼,可以被前置通知取代 before(); // 核心類的業務邏輯代碼 // Object returnValue = targer.method(args); Object returnValue = method.invoke(targer, args);//執行核心類的業務邏輯代碼 //后置代碼,可以被后置通知取代 after(); return returnValue; } private void before() { // 前置任務 System.out.println("[代理執行前置]洗澡"); System.out.println("[代理執行前置]化妝"); System.out.println("[代理執行前置]穿衣服"); System.out.println("*****************"); } private void after() { // 后置任務 System.out.println("*****************"); System.out.println("[代理執行后置]卸妝"); System.out.println("[代理執行后置]洗澡"); System.out.println("[代理執行后置]聽歌"); System.out.println(""); } }
4.測試
package aop006; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /* * 增加一個【靜態代理類】,類似與明星的經紀人 * 把核心的業務邏輯的代碼 和 非核心的 分離 * 把非核心的代碼交給經紀人(proxy)去管理, * 注意:經紀人和要明星,必須實現同一個接口 * * 缺點: * 1.每個點都需要寫一次 * 2.當分離出來的非核心代碼,如果一旦需要修改,那么全體都要改 */ public class Test { public static void main(String[] args) { //第一步:創建目標實現類的實例 Girl g1 = new Girl1(); Girl g2 = new Girl2(); //第二步:創建一個動態代理類(CEO 首席執行官) InvocationHandler handler1 = new GirlHandler(g1); InvocationHandler handler2 = new GirlHandler(g2); //第三步:創建動態代理(跟靜態代理一樣,申明的變量仍然是目標的接口) //創建一個宋喆 Girl girlProxy1 = (Girl) Proxy.newProxyInstance( g1.getClass().getClassLoader(), g1.getClass().getInterfaces(), handler1); girlProxy1.KFC("周六"); //對比 g1.KFC("周六"); 運行后的區別 girlProxy1.meet("周日"); Girl girlProxy2 = (Girl) Proxy.newProxyInstance( g2.getClass().getClassLoader(), g2.getClass().getInterfaces(), handler2); girlProxy2.KFC("周六"); //對比 g1.KFC("周六"); 運行后的區別 girlProxy2.meet("周日"); } }
- Spring配置聲明式事務
在還沒有學怎么配置聲明式事務的時候,我們每進行一次數據庫的更改操作都要手動的插入事務語句,這樣做不僅浪費時間,降低性能,也可能因為開發人員不當的操作而使數據庫不安全,例如
public class NewsDaoImpl implements NewsDao { @Autowired //@Qualifier("mySessionFactory") //@Resource(name="mySessionFactory") private SessionFactory sf; @Override public List showAllNews() { Session session = sf.openSession();//每一次,都創建1個新的session session.getTransaction().begin(); List<News> allNewList = new ArrayList<News>(); session.getTransaction().commit(); session.close(); return allNewList; } }
后來,用AOP技術在spirng配置文件中配置AOP去完成那些重復的操作,例如:
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="mod*" propagation="REQUIRED" /> <tx:method name="deleteSingleNews" propagation="REQUIRED" /> <tx:method name="*" propagation="REQUIRED" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="interceptorPointCuts" expression="execution(* news.dao.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" /> </aop:config>
注意:要導入AOP聯盟的四個包aopalliance-1.0.jar,aspectjrt.jar,aspectjweaver.jar,cglib-nodep-2.1_3.jar;
再后來,用spring注解配置聲明式事務,因為注解式更好讀懂,更好理解,更好配置,只要在配置文件中加上一個解析器。例如:
在spring配置文件加:
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager" />
然后再類的方法里加注解就可以啦
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import myNews6.dao.MyNews6Dao; import myNews6.entity.News; @Service @Scope("prototype") public class MyNews6ServiceImpl implements MyNews6Service { @Autowired private MyNews6Dao mnd; /* (non-Javadoc) * @see myNews6.service.MyNews6Service#getAllNews() */ @Override //事務注解要放到service層,因為AOP @Transactional(readOnly=true) public List getAllNews(){ List<News> list=mnd.getAllNews(); return list; } /* (non-Javadoc) * @see myNews6.service.MyNews6Service#deleteNewsById(java.lang.Integer) */ @Override @Transactional(readOnly=true) public String deleteNewsById(Integer id){ String value="deleteon"; value=mnd.deleteNewsById(id); return value; } }
這里需要注意的是:事務注解並是寫到被調用類的方法(目標類)里的,而是寫在調用的類的方法(代理類)里;
三、深入淺出Hibernate
- 什么是Hibernate?
Hibernate是一個開放源代碼的對象關系映射框架,它對JDBC進行了非常輕量級的對象封裝,使得Java程序員可以隨心所欲的使用對象編程思維來操縱數據庫。 Hibernate可以應用在任何使用JDBC的場合,既可以在Java的客戶端程序使用,也可以在Servlet/JSP的Web應用中使用,最具革命意義的是,Hibernate可以在應用EJB的J2EE架構中取代CMP,完成數據持久化的重任。Hibernate的核心接口一共有6個,分別為:Session、SessionFactory、Transaction、Query、Criteria和Configuration。
知道了Hibernate主要有哪些核心對象實現。我們再來看看Hibernate的設計架構,下面是個非常簡要的Hibernate架構圖:

Hibernate是持久層的一種實現,封裝了數據訪問等操作,實現了業務邏輯層和數據層進行的解耦;
- 什么是ORM?
ORM是OBject-Relational-Mapping 的縮寫,對象-關系映射(Object/Relation Mapping,簡稱ORM),是隨着面向對象的軟件開發方法發展而產生的。面向對象的開發方法是當今企業級應用開發環境中的主流開發方法,關系數據庫是企業級應用環境中永久存放數據的主流數據存儲系統。對象和關系數據是業務實體的兩種表現形式,業務實體在內存中表現為對象,在數據庫中表現為關系數據。內存中的對象之間存在關聯和繼承關系,而在數據庫中,關系數據無法直接表達多對多關聯和繼承關系。因此,對象-關系映射(ORM)系統一般以中間件的形式存在,主要實現程序對象到關系數據庫數據的映射。

- Hibernate的配置
Hibernate期初的配置方式是先配置hibernate.cfg.xml和*.hbm.xml.
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/CardDB</property> <property name="connection.username">root</property> <property name="connection.password">123456</property> <!-- 每個數據庫都有1個 --> <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property> <property name="connection.pool_size">5</property> <property name="show_sql">true</property> <property name="format_sql">true</property> <property name="hbm2ddl.auto">update</property> <!-- 指定一份映射文件 --> <mapping resource="ssh/entity/BookCard.hbm.xml"/> </session-factory> </hibernate-configuration>
然后在Spring配置文件里引入Hibernate.cfg.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd"> <!-- 類似於財務部門一樣,類就是錢,所有需要類的實例都由srping去管理 --> <bean id="myIndexAction" class="ssh.action.IndexAction" scope="prototype"> <!-- setIs(myIndexService) --> <property name="is" ref="myIndexService"/> </bean> <!-- myIndexService = new ssh.service.IndexServiceImpl() --> <bean id="myIndexService" class="ssh.service.IndexServiceImpl" scope="prototype"> <property name="id" ref="myIndexDao"/> </bean> <bean id="myIndexDao" class="ssh.dao.IndexDaoImpl" scope="prototype"> <!-- 晚點再注入能用的seesionFactory --> <property name="sessionFactory"><null/></property> </bean> </beans>
再后來直接在spring配置文件配置Hibernate,替換掉了Hibernate.cfg.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd"> <context:component-scan base-package="myNews6"></context:component-scan> <context:property-placeholder location="classpath:jdbc.properties" /> <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="mydataSource"></property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> <prop key="hibernate.connection.autocommit">false</prop> </props> </property> <property name="mappingResources"> <list> <value>News.hbm.xml</value> </list> </property> </bean> <bean id="mydataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> <!-- 每300秒檢查所有連接池中的空閑連接 --> <property name="idleConnectionTestPeriod" value="300"></property> <!-- 最大空閑時間,500秒內未使用則連接被丟棄。若為0則永不丟棄 --> <property name="maxIdleTime" value="500"></property> <!-- 最大連接數 --> <property name="maxPoolSize" value="1"></property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
在MVC結構里,我們使用Struts來實現里面的‘C’,也就是用之層。而Hibernate是實現‘M’層,Jsp則實現‘V’。當用戶訪問時,提交給Struts,然后Struts通過Service(Manage)層,然后Service(Manage)層訪問DAO層,DAO層通過Hibernate訪問數據庫。到現在一直沒用到Spring,Spring是干什么的呢?Spring就是控制業務邏輯層。例如:給Struts的action注入Service(Manage)層的對象,向Service(Manage)層注入DAO層的對象。通過Spring的AOP來控制Hibernate訪問數據庫的事務,來管理數據庫訪問事務。這就是SSH整合的基本思想。
