一、簡化Java開發
Spring為了降低Java開發的復雜性,采用了以下四種策略
-
基於POJO的輕量級和最小侵入性編程;
-
通過依賴注入和面向接口實現松耦合;
-
基於切面和慣例進行聲明式編程;
-
通過切面和模板減少樣板式代碼。
下面簡單介紹一下這四種策略分別是什么:
Spring竭力避免因自身的API而弄亂你的應用代碼。Spring不會強迫你實現Spring規范的接口或繼承Spring規范的類,相反,在基於Spring構建的應用中,它的類通常沒有任何痕跡表明你使用了Spring。最壞的場景是,一個類或許會使用Spring注解,但它依舊是POJO
2、依賴注入:
任何一個有實際意義的應用(肯定比Hello World示例更復雜)都會由兩個或者更多的類組成,這些類相互之間進行協作來完成特定的業務邏輯。按照傳統的做法,每個對象負責管理與自己相互協作的對象(即它所依賴的對象)的引用,這將會導致高度耦合和難以測試的代碼。通過DI,對象的依賴關系將由系統中負責協調各對象的第三方組件在創建對象的時候進行設定。對象無需自行創建或管理它們的依賴關系依賴關系將被自動注入到需要它們的對象當中去。
3、面向切面:
DI能夠讓相互協作的軟件組件保持松散耦合,而面向切面編程(aspect-oriented programming,AOP)允許你把遍布應用各處的功能分離出來形成可重用的組件。面向切面編程往往被定義為促使軟件系統實現關注點的分離一項技術。系統由許多不同的組件組成,每一個組件各負責一塊特定功能。除了實現自身核心的功能之外,這些組件還經常承擔着額外的職責。諸如日志、事務管理和安全這樣的系統服務經常融入到自身具有核心業務邏輯的組件中去,這些系統服務通常被稱為橫切關注點,因為它們會跨越系統的多個組件。
4、使用模板消除樣板式代碼:
有過java開發經驗的同學應該都知道在使用JDBC操作數據庫時的步驟有多繁瑣,下面我來看一下JDBC操作數據庫的代碼
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class Demo { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/person"; String user = "root"; String pwd = "admin"; String sql = "select * from student"; Connection conn = null; Statement st = null; ResultSet rs = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(url,user,pwd); st = conn.createStatement(); //執行查詢語句,另外也可以用execute(),代表執行任何SQL語句 rs = st.executeQuery(sql); while(rs.next()) { System.out.println(rs.getObject(1) + " " + rs.getObject(2) + " " + rs.getInt("birth")); } //分別捕獲異常 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { try { //判斷資源是否存在 if(rs != null) { rs.close(); //顯示的設置為空,提示gc回收 rs = null; } if(st != null) { st.close(); st = null; } if(conn != null) { conn.close(); conn = null; } } catch (SQLException e) { e.printStackTrace(); } } } }
估計很少有讀者能將這段代碼一行一行的看完,因為實在是太長了。
JDBC不是產生樣板式代碼的唯一場景。在許多編程場景中往往都會導致類似的樣板式代碼,JMS、JNDI和使用REST服務通常也涉及大量的重復代碼。Spring旨在通過模板封裝來消除樣板式代碼。Spring的JdbcTemplate使得執行數據庫操作時,避免傳統的JDBC樣板代碼成為了可能。
二、容納Bean
在基於Spring的應用中,你的應用對象生存於Spring容器(container)中。如下圖所示,Spring容器負責創建對象,裝配它們,配置它們並管理它們的整個生命周期,從生存到死亡(在這里,可能就是new到finalize())。
Spring容器是Spring的核心,Spring自帶了多個容器實現,可以歸為兩種不同的類型。bean工廠(由org.springframework.beans.factory.eanFactory接口定義)是最簡單的容器,提供基本的DI支持。應用上下文(由org.springframework.context.ApplicationContext接口定義)基於BeanFactory構建,並提供應用框架級別的服務,例如從屬性文件解析文本信息以及發布應用事件給感興趣的事件監聽者。通常我們使用的是應用上下文,因為bean工廠對大多數開發者來說功能比較薄弱。
1、使用應用上下文:
Spring自帶了多種類型的應用上下文
- AnnotationConfigApplicationContext:從一個或多個基於Java的配置類中加載Spring應用上下文。
- AnnotationConfigWebApplicationContext:從一個或多個基於Java的配置類中加載Spring Web應用上下文。
- ClassPathXmlApplicationContext:從類路徑下的一個或多個XML配置文件中加載上下文定義,把應用上下文的定義文件作為類資源。
- FileSystemXmlapplicationcontext:從文件系統下的一個或多個XML配置文件中加載上下文定義。
- XmlWebApplicationContext:從Web應用下的一個或多個XML配置文件中加載上下文定義
2、bean的生命周期:
在傳統的Java應用中,bean的生命周期很簡單。使用Java關鍵字new進行bean實例化,然后該bean就可以使用了。一旦該bean不再被使用,則由Java自動進行垃圾回收。相比之下,Spring容器中的bean的生命周期就顯得相對復雜多了
1、Spring對bean進行實例化;
2、Spring將值和bean的引用注入到bean對應的屬性中;
3、如果bean實現了BeanNameAware接口,Spring將bean的ID傳遞給setBean-Name()方法;
4、如果bean實現了BeanFactoryAware接口,Spring將調用setBeanFactory()方法,將BeanFactory容器實例傳入;
5、如果bean實現了ApplicationContextAware接口,Spring將調用setApplicationContext()方法,將bean所在的應用上下文的引用傳入進來;
6、如果bean實現了BeanPostProcessor接口,Spring將調用它們的post-ProcessBeforeInitialization()方法;
7、如果bean實現了InitializingBean接口,Spring將調用它們的after-PropertiesSet()方法。類似地,如果bean使用init-method聲明了初始化方法,該方法也會被調用;
8、如果bean實現了BeanPostProcessor接口,Spring將調用它們的post-ProcessAfterInitialization()方法;
9、此時,bean已經准備就緒,可以被應用程序使用了,它們將一直駐留在應用上下文中,直到該應用上下文被銷毀;
10、如果bean實現了DisposableBean接口,Spring將調用它的destroy()接口方法。同樣,如果bean使用destroy-method聲明了銷毀方法,該方法也會被調用
3、Spring的核心模塊:
我們來逐一分析一下Sping的各個組成模塊
Spring核心容器
容器是Spring框架最核心的部分,它管理着Spring應用中bean的創建、配置和管理。在該模塊中,包括了Spring bean工廠,它為Spring提供了DI的功能。基於bean工廠,我們還會發現有多種Spring應用上下文的實現,每一種都提供了配置Spring的不同方式。
除了bean工廠和應用上下文,該模塊也提供了許多企業服務,例如E-mail、JNDI訪問、EJB集成和調度。所有的Spring模塊都構建於核心容器之上。當你配置應用時,其實你隱式地使用了這些類。
Spring的AOP模塊
在AOP模塊中,Spring對面向切面編程提供了豐富的支持。這個模塊是Spring應用系統中開發切面的基礎。與DI一樣,AOP可以幫助應用對象解耦。借助於AOP,可以將遍布系統的關注點(例如事務和安全)從它們所應用的對象中解耦出來。
數據訪問和集成
使用JDBC編寫代碼通常會導致大量的樣板式代碼,例如獲得數據庫連接、創建語句、處理結果集到最后關閉數據庫連接。Spring的JDBC和DAO(Data Access Object)模塊抽象了這些樣板式代碼,使我們的數據庫代碼變得簡單明了,還可以避免因為關閉數據庫資源失敗而引發的問題。該模塊在多種數據庫服務的錯誤信息之上構建了一個語義豐富的異常層,以后我們再也不需要解釋那些隱晦專有的SQL錯誤信息了!
Web與遠程調用
MVC(Model-View-Controller)模式是一種普遍被接受的構建Web應用的方法,它可以幫助用戶將界面邏輯與應用邏輯分離。Java從來不缺少MVC框架,Apache的Struts、JSF、WebWork和Tapestry都是可選的最流行的MVC框架。
Spring裝配bean
一、Spring裝配的三種方式
1、在XML中進行顯示配置
2、在Java中進行顯示配置
3、隱式的bean的發現機制和自動裝配
至於哪一種裝配方式好,這里沒有統一的答案,讀者可以選擇適合自己的方案進行bean的裝配
二、自動化裝配bean
1、Spring 從兩個角度來實現自動化裝配bean :
- 組件掃描:Spring會自動發現應用上下文中所創建的bean
- 自動裝配:Spring自動滿足bean之間的依賴
2、創建可被發現的bean:
接下來我將用一個CD播放器案例來說明整個自動化裝配bean的過程,該項目是一個Maven項目,進行實驗前需要引入相關Maven配置文件,對Maven還不了解的同學建議去學習相關資料,這里不再贅述
第一步:創建CompactDisc接口,接口中包含了一個play()方法
package soundsystem; public interface CompactDisc { void play(); }
CompactDisc的具體內容並不重要,重要的是你將其定義為一個接口。作為接口,它定義了CD播放器對一盤CD所能進行的操作。它將CD播放器的任意實現與CD本身的耦合降低到了最小的程度。
第二步:我們還需要一個CompactDisc的實現,SgtPeppers實現了CompactDisc接口
package soundsystem; import org.springframework.stereotype.Component; @Component //該類是一個組件類 public class SgtPeppers implements CompactDisc { private String title = "曾經的你"; private String artist = "許巍"; @Override public void play() { System.out.println("Playing " + title + " by " + artist); } }
和CompactDisc接口一樣,Sgtpeppers的具體內容並不重要。你需要注意的就是SgtPeppers類上使用了@Component注解。這個簡單的注解表明該類會作為組件類,並告知Spring要為這個類創建bean。沒有必要顯式配置Sgtpeppers bean,因為這個類使用了@Component注解,所以Spring會為你把事情處理妥當。
第三步:我們聲明了組件類之后Spring並不知道這個組件類,所以我們還需要開啟組件掃描,讓Spring去查找相關的組件類。新建一個CDPlayerConfig類
package soundsystem; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration //Configuration注解表明該類是一個配置類 @ComponentScan //ComponentScan注解表示開啟組件掃描,默認是掃描當前包下的組件 public class CDPlayerConfig { /*@Bean public CompactDisc compactDisc() { return new SgtPeppers(); } @Bean public CDPlayer cdPlayer(CompactDisc compactDisc) { return new CDPlayer(compactDisc); }*/ }
第四步:下面我們來測試一下自動裝配是否成功
package soundsystem; 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.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=CDPlayerConfig.class) public class CDPlayTest { @Autowired //@Autowired注解將CompactDisc注入到測試代碼中,稍后會講解到 private CompactDisc compactDisc; @Test public void play() { compactDisc.play(); } }
運行結果:
Playing 曾經的你 by 許巍
我們看到結果正常輸出,說明我們的自動裝配成功
3、自動裝配中的注解介紹:
@Component:
@Component注解表明該類作為組件類,並告知Spring要為這個類創建bean,另外@Component中還可以傳入一個參數,用於為這個bean設置ID,如果你之前有過通過xml文件配置bean的經驗話就知道在配置bean的時候就需要設置bean的id。
@Component("cdplay"),這個注解表明當前bean的ID為cdplay
@ComponentScan:
@ComponentScan默認會掃描與配置類相同的包,比如上面的程序中因為CDPlayerConfig類位於soundsystem包中,因此Spring將會掃描這個包以及這個包下的所有子包,查找帶有@Component注解的類。這樣的話,就能發現CompactDisc,並且會在Spring中自動為其創建一個bean。我們也可以為該注解傳入參數,讓其掃描指定的包:@ComponentScan(basePackages={"soundsystem","video"}),掃描soundsystem和video包下的組件
//直接在value屬性中指明包的名稱 @Configuration @ComponentScan("soundsystem") public class CDPlayerConfig{} //通過basePackages屬性配置 @Configuration @ComponentScan(basePackages="soundsystem") public class CDPlayerConfig{} //設置多個基礎包,用數組表示 @Configuration @ComponentScan(basePackages={"soundsystem","video"}) public class CDPlayerConfig{} //基礎包以String類型表示是不安全的,如果重構代碼的話,指定的基礎包可能會出現錯誤,用指定為包中所包含的類或接口的方法 @Configuration @ComponentScan(basePackageClasses={CDPlayer.class,DVDPlayer.class}) public class CDPlayerConfig{}
@Autowired:
簡單來說,自動裝配就是讓Spring自動滿足bean依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需求的其他bean。為了聲明要進行自動裝配,我們可以借助Spring的@Autowired注解。
比如上面的測試代碼添加了@Autowired注解,這表明當Spring創建CDPlayer bean的時候,會通過這個構造器來進行實例化並且會傳入一個可設置給CompactDisc類型的bean。
@Component public class CDPlayer implements MediaPlayer{ private CompactDisc cd; @Autowired//這表明當Spring創建CDPlayer bean的時候,會通過這個構造器來進行實例化並且會傳入一個可設置給CompactDisc類型的bean. public CDPlayer(CompactDisc cd){//構造器 this.cd = cd; } public void paly(){ cd.paly(); } }
@Autowired注解不僅能夠用在構造器上,還能用在屬性的Setter方法上.比如說,如果CDPlayer有一個setCompactDisc()方法,那么可以采用如下的注解形式進行自動裝配:
@Autowired public void setCompactDisc(CompactDisc cd){ this.cd = cd; }
但是如果沒有匹配的bean,那么在應用上下文創建的時候,Spring會拋出一個異常,為了避免異常的出現,你可以將@Autowired的requied屬性設置為false
@Autowired(required=false) public void setCompactDisc(CompactDisc cd){ this.cd = cd; }
三、通過Java代碼裝配bean
盡管在很多場景下通過組件掃描和自動裝配實現Spring的自動化掃描配置是更為推薦的方式,但在有些情況下自動化掃描的方案行不通,如想要將第三方庫中的組件裝配到自己的應用中。在這種情況下必須通過顯示 裝配的方式。
顯示裝配有兩種可選方案:Java和XML。JavaConfig是更好的方案:更強大、類型安全並對重構友好。因他就是Java代碼。
還是上面的那個案例,我們現在將它用Java代碼來實現裝配bean
接口:CompactDisc.java
package soundsystem; public interface CompactDisc { void play(); }
接口: MediaPlayer.java
package soundsystem; public interface MediaPlayer { void play(); }
CompactDisc的實現類:SgtPeppers.java
package soundsystem; public class SgtPeppers implements CompactDisc { private String title = "曾經的你"; private String artist = "許巍"; @Override public void play() { System.out.println("Playing " + title + " by " + artist); } }
細心的讀者可以發現,這里我們去掉了@Compenent注解
MediaPlayer的實現類:CDPlayer.java
package soundsystem; import org.springframework.beans.factory.annotation.Autowired; public class CDPlayer implements MediaPlayer { private CompactDisc cd; @Autowired public CDPlayer(CompactDisc cd) { this.cd = cd; } @Override public void play() { cd.play(); } }
借助JavaConfig實現注入
CDPlayerConfig.java
package soundsystem; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CDPlayerConfig { @Bean public CompactDisc compactDisc() { return new SgtPeppers(); } @Bean public CDPlayer cdPlayer(CompactDisc compactDisc) { return new CDPlayer(compactDisc); } }
區別與自動裝配,這里去掉了@ComponentScan注解,而是顯式的聲明了Bean。@Bean注解告訴了Spring上下文這個方法會將返回一個對象,該對象要注冊為Spring應用上下文中的bean,方法體重包含了最終產生bean實例的實現邏輯。
測試類:CDPlayerTest.java
package soundsystem; 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.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=CDPlayerConfig.class) public class CDPlayerTest { @Autowired private MediaPlayer player; @Test public void play() { player.play(); } }
依然可以得出正確的輸出結果
四、通過XML裝配bean
XML 配置是最原始最古老的 Bean 的裝配方案,曾經我們的項目離不開它,而如今,我們卻在慢慢的拋棄它,沒辦法,時代在進步,我們也要進步呀。為了能看懂前輩們寫的代碼,我們還是有必要來看一下如何通過 XML 來裝配 Bean。
創建一個Book類:
package xmlTest; public class Book { private Integer id; private String name; private Double price; public Book() { } public Book(Integer id, String name, Double price) { this.id = id; this.name = name; this.price = price; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } }
然后再在 resources 目錄下(用IDEA創建maven項目時會有一個resources文件夾)創建一個 beans.xml 文件,作為 Spring 的配置文件,然后在里邊配置一個 Book bean,如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.javaboy.spring.Book" id="book"/> </beans>
在這里,我們在 class 屬性中配置類的全路徑,id 則表示 bean 的名稱,也可以通過 name 屬性來指定 bean 的名稱,大部分場景下兩者無任何差別,會有一些特殊場景下(例如用,隔開多個實例名,兩者的處理方案不同),兩者有區別。
接下來新建一個測試類,看一下我們的配置是否正確:
package xmlTest; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BookTest { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:bean.xml"); Book book = (Book) ctx.getBean("book"); System.out.println(book); } }
輸出結果:xmlTest.Book@e45f292
在配置 Bean 時,給 Bean 指定相關的屬性值,我們有幾種不同的方式:
1.構造方法指定:
首先我們可以通過構造方法指定 bean 的屬性值,前提是我們為 Book 類提供一個有參構造方法(大家在創建有參構造方法時,一定記得再順手加一個無參構造方法):
public class Book { private Integer id; private String name; private Double price; public Book() { } public Book(Integer id, String name, Double price) { this.id = id; this.name = name; this.price = price; } //省略 getter/setter }
然后在 XML 文件中,我們就可以通過構造方法注入相關值了:
<bean class="org.javaboy.spring.Book" id="book2"> <constructor-arg name="id" value="99"/> <constructor-arg name="name" value="三國演義"/> <constructor-arg name="price" value="99"/> </bean>
使用構造方法注入相關值的時候,也可以使用下標來描述參數的順序,注意如果使用下標,參數順序不能錯:
<bean class="org.javaboy.spring.Book" id="book3"> <constructor-arg index="0" value="99"/> <constructor-arg index="1" value="紅樓夢"/> <constructor-arg index="2" value="100"/> </bean>
注入成功之后,當我們再次去獲取 Bean 的時候,就可以看到這些屬性了。
2.通過屬性注入:
<bean class="org.javaboy.spring.Book" id="book4"> <property name="id" value="99"/> <property name="name" value="水滸傳"/> <property name="price" value="99"/> </bean>
Spring的面向切面(AOP)
一、面向切面編程簡介
二、AOP術語:
與大多數技術一樣,AOP已經形成了自己的術語。描述切面的常用術語有通知(advice)、切點(pointcut)和連接點(join point)。初學者在學習AOP時最頭疼的就是理解這些概念,包括我自己也是這樣,網上面的一些博客在介紹AOP時也只是從定義角度去解釋,不太容易理解,下面我將用一個日常生活中的例子來向大家介紹AOP術語,方便大家理解。
我們現在每家每戶都需要用電,那么用電就會涉及到用電量和電費,在10年前科技還沒有如此發達的時候,我們的電費都是需要專門的人員來收取的,電力公司會安排人員去到不同的地區進行電表的查看和電費的收取。
通知(Advice):
當抄表員出現在我們家門口時,他們要登記用電量並回去向電力公司報告。顯然,他們必須有一份需要抄表的住戶清單,他們所匯報的信息也很重要,但記錄用電量才是抄表員的主要工作。類似的,切面也有目標——它必須要完成的工作。在AOP術語中,切面的工作被稱為通知。
Spring切面可以應用5種類型的通知:
- 前置通知(Before):在目標方法被調用之前調用通知功能;
- 后置通知(After):在目標方法完成之后調用通知,此時不會關心方法的輸出是什么;
- 返回通知(After-returning):在目標方法成功執行之后調用通知;
- 異常通知(After-throwing):在目標方法拋出異常后調用通知;
- 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執行自定義的行為。
連接點(Join Point):
電力公司為多個住戶提供服務,甚至可能是整個城市。每家都有一個電表,這些電表上的數字都需要讀取,因此每家都是抄表員的潛在目標。
同樣,我們的應用可能也有數以千計的時機應用通知。這些時機被稱為連接點。連接點是在應用執行過程中能夠插入切面的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼可以利用這些點插入到應用的正常流程之中,並添加新的行為。
切點(Poincut):
如果讓一個工作人員去抄寫全市的所有電表,那么肯定是不現實的。實際上,電力公司會安排每一個抄表員負責一個地區的抄表工作,比如小宋負責陽光小區的抄表工作,小馬負責歐風小區的抄表工作。類似的,切點也是如此,一個程序中可能有很多連接點,那么並不是每個連接點我們都需要通知,我們只需要通知部分的連接點即可,那么切點就是這個我們通知的連接點。
切面(Aspect):
當抄表員開始一天的工作時,他知道自己要做的事情(報告用電量)和從哪些房屋收集信息。因此,他知道要完成工作所需要的一切東西。
切面是通知和切點的結合。通知和切點共同定義了切面的全部內容——它是什么,在何時和何處完成其功能。
三、Spring對AOP的支持
1、動態代理:
Spring AOP構建在動態代理之上,也就是說,Spring運行時會為目標對象動態創建代理對象。代理類封裝了目標類,並攔截被通知方法的調用,再把調用轉發給真正的目標bean。當代理類攔截到方法調用時,在調用目標bean方法之前,會執行切面邏輯。
2、織入切面的時間:
通過在代理類中包裹切面,Spring在運行期把切面織入到Spring 管理的bean中,也就是說,直到應用需要被代理的bean時,Spring才會創建代理對象。
因為Spring運行時才創建代理對象,所以我們不需要特殊的編譯器來織入Spring AOP切面。
四、Spring AOP的使用
假設我們有個現場表演的接口Performance和它的實現類SleepNoMore:
package PerformTest; /** * 現場表演,如舞台劇,電影,音樂會 */ public interface Performance { void perform(); }
package PerformTest; import org.springframework.stereotype.Component; /** * 戲劇:《哈姆雷特》 */ @Component public class SleepNoMore implements Performance { @Override public void perform() { System.out.println("戲劇《哈姆雷特》"); } }
既然是演出,就需要觀眾,假設我們的需求是:在看演出之前,觀眾先入座並將手機調整至靜音,在觀看演出之后觀眾鼓掌,如果演出失敗觀眾退票,我們當然可以把這些邏輯寫在上面的perform()方法中,但不推薦這么做,因為這些邏輯理論上和演出的核心無關,就算觀眾不將手機調整至靜音或者看完演出不鼓掌,都不影響演出的進行。
針對這個需求,我們可以使用AOP來實現。
1、定義切面:
定義一個觀眾的切面,並聲明前置、后置和異常通知:
package PerformTest; import org.aspectj.lang.annotation.*; /** * 觀眾 * 使用@Aspect注解定義為切面 */ @Aspect //@Aspect注解表明Audience類是一個切面。 public class Audience { /** * 表演之前,觀眾就座,前置通知 */ @Before("execution(* PerformTest.Performance.perform(..))") public void takeSeats() { System.out.println("Taking seats"); } /** * 表演之前,將手機調至靜音,前置通知 */ @Before("execution(* PerformTest.Performance.perform(..))") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } /** * 表演結束,不管表演成功或者失敗,后置通知 */ @After("execution(* PerformTest.Performance.perform(..))") public void finish() { System.out.println("perform finish"); } /** * 表演之后,鼓掌,后置通知 */ @AfterReturning("execution(* PerformTest.Performance.perform(..))") public void applause() { System.out.println("CLAP CLAP CLAP!!!"); } /** * 表演失敗之后,觀眾要求退款,異常通知 */ @AfterThrowing("execution(* PerformTest.Performance.perform(..))") public void demandRefund() { System.out.println("Demanding a refund"); } }
@Before:該注解用來定義前置通知,通知方法會在目標方法調用之前執行
@After:該注解用來定義后置通知,通知方法會在目標方法調用之后執行
@AfterReturning:該注解用來定義返回通知,通知方法會在目標方法返回后調用
@AfterThrowing:該注解用來定義異常通知,通知方法會在目標方法拋出異常后調用
execution(* PerformTest.Performance.perform(..))含義:
execution:在方法執行時觸發
*:表明我們不關心方法返回值的類型,即可以是任意類型
.PerformTest.performance:使用全限定類名和方法名指定要添加前置通知的方法
(..):方法的參數列表使用(..),表明我們不關心方法的入參是什么,即可以是任意類型
2、定義配置類:
package PerformTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration //配置類注解 @EnableAspectJAutoProxy //啟用自動代理 @ComponentScan //啟用掃描 public class ConcertConfig { @Bean public Audience audience() { return new Audience(); } }
3、定義測試類:
package PerformTest; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class PerformTest { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConcertConfig.class); Performance performance = context.getBean(Performance.class); performance.perform(); context.close(); } }
運行結果:
Silencing cell phones Taking seats 戲劇《哈姆雷特》 perform finish CLAP CLAP CLAP!!!
從結果可以看出在戲劇《哈姆雷特》的前面和后面都有語句輸出,這就是Spring AOP的通知起了作用
SpringMVC
一、SpringMVC的執行流程
1、請求旅程的第一站是Spring的DispatcherServlet。與大多數基於Java的Web框架一樣,Spring MVC所有的請求都會通過一個前端控制器(front controller)Servlet。前端控制器是常用的Web應用程序模式,在這里一個單實例的Servlet將請求委托給應用程序的其他組件來執行實際的處理。在SpringMVC中DispatcherServlet就是前端控制器。
2、Dispatcherservlet的任務是將請求發送給Spring MVC控制器(controller),控制器是一個用於處理請求的Spring組件。在典型的應用程序中可能會有多個控制器,Dispatcherservlet需要知道應該將請求發送給哪個控制器。所以DispatcherServlet以會查詢一個或多個處理器映射(handler mapping)來確定請求的下一站在哪里。處理器映射會根據請求所攜帶的URL信息來進行決策。
3、一旦選擇了合適的控制器,Dispatcherservlet會將請求發送給選中的控制器。到了控制器,請求會卸下其負載(用戶提交的信息)並耐心等待控制器處理這些信息。
4、控制器在完成邏輯處理后,通常會產生一些信息,這些信息需要返回給用戶並在瀏覽器上顯示。這些信息被稱為模型(model)。不過僅僅給用戶返回原始的信息是不夠的——這些信息需要以用戶友好的方式進行格式化,一般會是HTML。所以,信息需要發送給一個視圖(view),通常會是JSP。控制器所做的最后一件事就是將模型數據打包,並且標示出用於渲染輸出的視圖名。它接下來會將請求連同模型和視圖名發送回DispatcherServlete。
5、這樣,控制器就不會與特定的視圖相耦合,傳遞給DispatcherServlet的視圖名並不直接表示某個特定的JSP。實際上,它甚至並不能確定視圖就是JSP。相反,它僅僅傳遞了一個邏輯名稱,這個名字將會用來查找產生結果的真正視圖。Dispatcherservlet將會使用視圖解析器(view resolver)來將邏輯視圖名匹配為一個特定的視圖實現,它可能是也可能不是JSP。
6、既然Dispatcherservlet已經知道由哪個視圖渲染結果,那請求的任務基本上也就完成了。它的最后一站是視圖的實現(可能是JSP)
7、在這里它交付模型數據。請求的任務就完成了。視圖將使用模型數據渲染輸出,這個輸出會通過響應對象傳遞給客戶端(不會像聽上去那樣硬編碼)
二、SpingMVC初探
1、首先用maven創建一個web項目(我這里用的是idea),創建完成后的項目目錄結構如下:
2、在pom.xml文件中引入spring相關jar包:
<dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.6.RELEASE</version> </dependency> </dependencies>
3、在web.xml中進行SpringMVC的相關配置:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <!--配置前端控制器 DispatcherServlet--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--ContextconfigLocation配置springmvc加載的配置文件 適配器、處理器映射器--> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!-- <load-on-startup>1</load-on-startup> --> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 配置解析路徑 1、.action訪問以.action結尾的 由DispatcherServlet進行解析 2、/,所有訪問都由DispatcherServlet進行解析 --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
4、在resources目錄下新建springmvc-servlet.xml文件,如果沒有resources目錄的話自己新建一個resources目錄,並將其設置為Resources Root:
springmvc-servlet.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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd"> <!--自動掃描方式,掃描包下面的所有的Controller 使用組件掃描的方式可以一次掃描多個Controller--> <context:component-scan base-package="test.SpringMVC"/> <!-- don't handle the static resource --> <mvc:default-servlet-handler /> <!--配置注解的處理器映射器和處理器適配器--> <mvc:annotation-driven /> <!--配置視圖解析器 在Controller中設置視圖名的時候會自動加上前綴和后綴。--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前綴 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后綴 --> <property name="suffix" value=".jsp" /> </bean> </beans>
5、在WEB-INF目錄下新建一個文件夾jsp,然后在jsp文件夾中創建一個hello.jsp:
<%-- Created by IntelliJ IDEA. User: wydream Date: 2020/1/2 Time: 17:13 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>hello</title> </head> <body> Hello World </body> </html>
6、新建一個Controller類:
package test.SpingMvc; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/mvc") public class mvcController { @RequestMapping("hello") public String hello() { return "hello"; } }
7、配置tomcat服務器並啟動項目:
8、在瀏覽器輸入http://localhost:8080/項目名/mvc/hello即可訪問
三、配置文件詳解
1、web.xml配置:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" 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_3_0.xsd"> <display-name></display-name> <!-- 404錯誤攔截 --> <error-page> <error-code>404</error-code> <location>/error404.jsp</location> </error-page> <!-- 500錯誤攔截 --> <error-page> <error-code>500</error-code> <location>/error500.jsp</location> </error-page> <!-- 加載spring容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/classes/spring/applicationContext-*.xml</param-value> </context-param> <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> <!-- ContextconfigLocation配置springmvc加載的配置文件 適配器、處理映射器等 --> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/classes/spring/springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <!-- 1、.action訪問以.action結尾的 由DispatcherServlet進行解析 2、/,所有訪問都由DispatcherServlet進行解析 --> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 解決post亂碼問題的過濾器 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>welcome.jsp</welcome-file> </welcome-file-list> </web-app>
2、springmvc.xml文件的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置視圖解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 使用前綴和后綴 --> <property name="prefix" value="/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 使用組件掃描的方式可以一次掃描多個Controller --> <context:component-scan base-package="com.wxisme.ssm.controller"> </context:component-scan> <!-- 配置注解的處理器映射器和處理器適配器 --> <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven> <!-- 自定義參數類型綁定 --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <list> <!-- 日期類型綁定 --> <bean class="com.wxisme.ssm.controller.converter.DateConverter"/> </list> </property> </bean> <!-- 訪問靜態資源文件 --> <!-- <mvc:default-servlet-handler/> 需要在web.xml中配置--> <mvc:resources mapping="/images/**" location="/images/" /> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/js/**" location="/js/" /> <mvc:resources mapping="/imgdata/**" location="/imgdata/" /> <!-- 定義攔截器 --> <mvc:interceptors> <!-- 直接定義攔截所有請求 --> <bean class="com.wxisme.ssm.interceptor.IdentityInterceptor"></bean> <!-- <mvc:interceptor> 攔截所有路徑的請求 包括子路徑 <mvc:mapping path="/**"/> <bean class="com.wxisme.ssm.interceptor.IdentityInterceptor"></bean> </mvc:interceptor> --> </mvc:interceptors> <!--配置上傳文件數據解析器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize"> <value>9242880</value> </property> </bean> <!-- 定義全局異常處理器 --> <!-- 只有一個全局異常處理器起作用 --> <bean id="exceptionResolver" class="com.wxisme.ssm.exception.OverallExceptionResolver"></bean> </beans>
3、applicationContext-*.xml的配置:
applicationContext-*.xml包括三個配置文件,分別對應數據層控制、業務邏輯service控制和事務的控制。
數據訪問層的控制,applicationContext-dao.xml的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 加載數據庫連接的資源文件 --> <context:property-placeholder location="/WEB-INF/classes/jdbc.properties"/> <!-- 配置數據源 dbcp數據庫連接池 --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 配置sqlSessionFactory
Spring和MyBatis整合配置,jar包由MyBatis提供。
--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 數據庫連接池 --> <property name="dataSource" ref="dataSource"/> <!-- 加載Mybatis全局配置文件 --> <property name="configLocation" value="/WEB-INF/classes/mybatis/SqlMapConfig.xml"/> </bean> <!-- 配置mapper掃描器,掃描mapper包下的所有mapper文件和類,要求mapper配置文件和類名需要一致。-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 掃描包路徑,如果需要掃描多個包中間用半角逗號隔開 --> <property name="basePackage" value="com.wxisme.ssm.mapper"></property> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> </beans>
事務控制,applicationContext-transaction.xml的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 事務控制 對MyBatis操作數據庫 spring使用JDBC事務控制類 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 配置數據源 --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 傳播行為 --> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> <tx:method name="select*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <!-- 配置aop --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.wxisme.ssm.service.impl.*.*(..))"/> </aop:config> </beans>
4、 MyBatis的配置:
SqlMapConfig.xml的配置 全局setting配置這里省略,數據庫連接池在spring整合文件中已經配置,具體setting配置參考官方文檔。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 將數據庫連接數據抽取到屬性文件中方便測試 --> <!-- <properties resource="/WEB-INF/classes/jdbc.properties"></properties> --> <!-- 別名的定義 --> <typeAliases> <!-- 批量定義別名 ,指定包名,自動掃描包中的類,別名即為類名,首字母大小寫無所謂--> <package name="com.wxisme.ssm.po"/> </typeAliases> <!-- 數據庫連接用數據庫連接池 --> <mappers> <!-- 通過掃描包的方式來進行批量加載映射文件 --> <package name="com.wxisme.ssm.mapper"/> </mappers> </configuration>