柚子快報邀請碼778899分享:數據庫 java
柚子快報邀請碼778899分享:數據庫 java
Spring為了降低Java開發(fā)的復雜性,采用了以下四種策略
基于POJO的輕量級和最小侵入性編程; 通過依賴注入和面向接口實現(xiàn)松耦合; 基于切面和慣例進行聲明式編程; 通過切面和模板減少樣板式代碼。
下面簡單介紹一下這四種策略分別是什么:
1、激發(fā)POJO的潛能:
Spring竭力避免因自身的API而弄亂你的應用代碼。Spring不會強迫你實現(xiàn)Spring規(guī)范的接口或繼承Spring規(guī)范的類,相反,在基于Spring構建的應用中,它的類通常沒有任何痕跡表明你使用了Spring。最壞的場景是,一個類或許會使用Spring注解,但它依舊是POJO
2、依賴注入:
任何一個有實際意義的應用(肯定比Hello World示例更復雜)都會由兩個或者更多的類組成,這些類相互之間進行協(xié)作來完成特定的業(yè)務邏輯。按照傳統(tǒng)的做法,每個對象負責管理與自己相互協(xié)作的對象(即它所依賴的對象)的引用,這將會導致高度耦合和難以測試的代碼。通過DI,對象的依賴關系將由系統(tǒng)中負責協(xié)調各對象的第三方組件在創(chuàng)建對象的時候進行設定。對象無需自行創(chuàng)建或管理它們的依賴關系依賴關系將被自動注入到需要它們的對象當中去。
3、面向切面:
DI能夠讓相互協(xié)作的軟件組件保持松散耦合,而面向切面編程(aspect-oriented programming,AOP)允許你把遍布應用各處的功能分離出來形成可重用的組件。面向切面編程往往被定義為促使軟件系統(tǒng)實現(xiàn)關注點的分離一項技術。系統(tǒng)由許多不同的組件組成,每一個組件各負責一塊特定功能。除了實現(xiàn)自身核心的功能之外,這些組件還經常承擔著額外的職責。諸如日志、事務管理和安全這樣的系統(tǒng)服務經常融入到自身具有核心業(yè)務邏輯的組件中去,這些系統(tǒng)服務通常被稱為橫切關注點,因為它們會跨越系統(tǒng)的多個組件。
?編輯
4、使用模板消除樣板式代碼:
有過java開發(fā)經驗的同學應該都知道在使用JDBC操作數據庫時的步驟有多繁瑣,下面我來看一下JDBC操作數據庫的代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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(); ????????????//執(zhí)行查詢語句,另外也可以用execute(),代表執(zhí)行任何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使得執(zhí)行數據庫操作時,避免傳統(tǒng)的JDBC樣板代碼成為了可能。
1|2二、容納Bean
在基于Spring的應用中,你的應用對象生存于Spring容器(container)中。如下圖所示,Spring容器負責創(chuàng)建對象,裝配它們,配置它們并管理它們的整個生命周期,從生存到死亡(在這里,可能就是new到finalize())。?編輯
Spring容器是Spring的核心,Spring自帶了多個容器實現(xiàn),可以歸為兩種不同的類型。bean工廠(由org.springframework.beans.factory.eanFactory接口定義)是最簡單的容器,提供基本的DI支持。應用上下文(由org.springframework.context.ApplicationContext接口定義)基于BeanFactory構建,并提供應用框架級別的服務,例如從屬性文件解析文本信息以及發(fā)布應用事件給感興趣的事件監(jiān)聽者。通常我們使用的是應用上下文,因為bean工廠對大多數開發(fā)者來說功能比較薄弱。
1、使用應用上下文:
?Spring自帶了多種類型的應用上下文
AnnotationConfigApplicationContext:從一個或多個基于Java的配置類中加載Spring應用上下文。AnnotationConfigWebApplicationContext:從一個或多個基于Java的配置類中加載Spring Web應用上下文。ClassPathXmlApplicationContext:從類路徑下的一個或多個XML配置文件中加載上下文定義,把應用上下文的定義文件作為類資源。FileSystemXmlapplicationcontext:從文件系統(tǒng)下的一個或多個XML配置文件中加載上下文定義。XmlWebApplicationContext:從Web應用下的一個或多個XML配置文件中加載上下文定義
2、bean的生命周期:
在傳統(tǒng)的Java應用中,bean的生命周期很簡單。使用Java關鍵字new進行bean實例化,然后該bean就可以使用了。一旦該bean不再被使用,則由Java自動進行垃圾回收。相比之下,Spring容器中的bean的生命周期就顯得相對復雜多了
?編輯
1、Spring對bean進行實例化;
2、Spring將值和bean的引用注入到bean對應的屬性中;
3、如果bean實現(xiàn)了BeanNameAware接口,Spring將bean的ID傳遞給setBean-Name()方法;
4、如果bean實現(xiàn)了BeanFactoryAware接口,Spring將調用setBeanFactory()方法,將BeanFactory容器實例傳入;
5、如果bean實現(xiàn)了ApplicationContextAware接口,Spring將調用setApplicationContext()方法,將bean所在的應用上下文的引用傳入進來;
6、如果bean實現(xiàn)了BeanPostProcessor接口,Spring將調用它們的post-ProcessBeforeInitialization()方法;
7、如果bean實現(xiàn)了InitializingBean接口,Spring將調用它們的after-PropertiesSet()方法。類似地,如果bean使用init-method聲明了初始化方法,該方法也會被調用;
8、如果bean實現(xiàn)了BeanPostProcessor接口,Spring將調用它們的post-ProcessAfterInitialization()方法;
9、此時,bean已經準備就緒,可以被應用程序使用了,它們將一直駐留在應用上下文中,直到該應用上下文被銷毀;
10、如果bean實現(xiàn)了DisposableBean接口,Spring將調用它的destroy()接口方法。同樣,如果bean使用destroy-method聲明了銷毀方法,該方法也會被調用
3、Spring的核心模塊:
?編輯
?我們來逐一分析一下Sping的各個組成模塊
Spring核心容器
容器是Spring框架最核心的部分,它管理著Spring應用中bean的創(chuàng)建、配置和管理。在該模塊中,包括了Spring bean工廠,它為Spring提供了DI的功能?;赽ean工廠,我們還會發(fā)現(xiàn)有多種Spring應用上下文的實現(xiàn),每一種都提供了配置Spring的不同方式。
除了bean工廠和應用上下文,該模塊也提供了許多企業(yè)服務,例如E-mail、JNDI訪問、EJB集成和調度。所有的Spring模塊都構建于核心容器之上。當你配置應用時,其實你隱式地使用了這些類。
Spring的AOP模塊
?在AOP模塊中,Spring對面向切面編程提供了豐富的支持。這個模塊是Spring應用系統(tǒng)中開發(fā)切面的基礎。與DI一樣,AOP可以幫助應用對象解耦。借助于AOP,可以將遍布系統(tǒng)的關注點(例如事務和安全)從它們所應用的對象中解耦出來。
數據訪問和集成
使用JDBC編寫代碼通常會導致大量的樣板式代碼,例如獲得數據庫連接、創(chuàng)建語句、處理結果集到最后關閉數據庫連接。Spring的JDBC和DAO(Data Access Object)模塊抽象了這些樣板式代碼,使我們的數據庫代碼變得簡單明了,還可以避免因為關閉數據庫資源失敗而引發(fā)的問題。該模塊在多種數據庫服務的錯誤信息之上構建了一個語義豐富的異常層,以后我們再也不需要解釋那些隱晦專有的SQL錯誤信息了!
Web與遠程調用
?MVC(Model-View-Controller)模式是一種普遍被接受的構建Web應用的方法,它可以幫助用戶將界面邏輯與應用邏輯分離。Java從來不缺少MVC框架,Apache的Struts、JSF、WebWork和Tapestry都是可選的最流行的MVC框架。
2|0Spring裝配bean
2|1一、Spring裝配的三種方式
1、在XML中進行顯示配置
2、在Java中進行顯示配置
3、隱式的bean的發(fā)現(xiàn)機制和自動裝配
至于哪一種裝配方式好,這里沒有統(tǒng)一的答案,讀者可以選擇適合自己的方案進行bean的裝配
2|2二、自動化裝配bean
1、Spring 從兩個角度來實現(xiàn)自動化裝配bean :
組件掃描:Spring會自動發(fā)現(xiàn)應用上下文中所創(chuàng)建的bean自動裝配:Spring自動滿足bean之間的依賴
2、創(chuàng)建可被發(fā)現(xiàn)的bean:
接下來我將用一個CD播放器案例來說明整個自動化裝配bean的過程,該項目是一個Maven項目,進行實驗前需要引入相關Maven配置文件,對Maven還不了解的同學建議去學習相關資料,這里不再贅述
第一步:創(chuàng)建CompactDisc接口,接口中包含了一個play()方法
1 2 3 4 5 package?soundsystem; public?interface?CompactDisc { ????void?play(); }
CompactDisc的具體內容并不重要,重要的是你將其定義為一個接口。作為接口,它定義了CD播放器對一盤CD所能進行的操作。它將CD播放器的任意實現(xiàn)與CD本身的耦合降低到了最小的程度。
第二步:我們還需要一個CompactDisc的實現(xiàn),SgtPeppers實現(xiàn)了CompactDisc接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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要為這個類創(chuàng)建bean。沒有必要顯式配置Sgtpeppers bean,因為這個類使用了@Component注解,所以Spring會為你把事情處理妥當。
第三步:我們聲明了組件類之后Spring并不知道這個組件類,所以我們還需要開啟組件掃描,讓Spring去查找相關的組件類。新建一個CDPlayerConfig類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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); ????}*/ }
第四步:下面我們來測試一下自動裝配是否成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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(); ????} }
運行結果:
1 Playing 曾經的你 by 許巍
我們看到結果正常輸出,說明我們的自動裝配成功
3、自動裝配中的注解介紹:
@Component:
@Component注解表明該類作為組件類,并告知Spring要為這個類創(chuàng)建bean,另外@Component中還可以傳入一個參數,用于為這個bean設置ID,如果你之前有過通過xml文件配置bean的經驗話就知道在配置bean的時候就需要設置bean的id。
@Component("cdplay"),這個注解表明當前bean的ID為cdplay
@ComponentScan:
@ComponentScan默認會掃描與配置類相同的包,比如上面的程序中因為CDPlayerConfig類位于soundsystem包中,因此Spring將會掃描這個包以及這個包下的所有子包,查找?guī)в蠤Component注解的類。這樣的話,就能發(fā)現(xiàn)CompactDisc,并且會在Spring中自動為其創(chuàng)建一個bean。我們也可以為該注解傳入參數,讓其掃描指定的包:@ComponentScan(basePackages={"soundsystem","video"}),掃描soundsystem和video包下的組件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //直接在value屬性中指明包的名稱 @Configuration @ComponentScan("soundsystem") public?class?CDPlayerConfig{} //通過basePackages屬性配置 @Configuration @ComponentScan(basePackages="soundsystem") public?class?CDPlayerConfig{} //設置多個基礎包,用數組表示 @Configuration @ComponentScan(basePackages={"soundsystem","video"}) public?class?CDPlayerConfig{} //基礎包以String類型表示是不安全的,如果重構代碼的話,指定的基礎包可能會出現(xiàn)錯誤,用指定為包中所包含的類或接口的方法 @Configuration @ComponentScan(basePackageClasses={CDPlayer.class,DVDPlayer.class}) public?class?CDPlayerConfig{}
@Autowired:
簡單來說,自動裝配就是讓Spring自動滿足bean依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需求的其他bean。為了聲明要進行自動裝配,我們可以借助Spring的@Autowired注解。
比如上面的測試代碼添加了@Autowired注解,這表明當Spring創(chuàng)建CDPlayer bean的時候,會通過這個構造器來進行實例化并且會傳入一個可設置給CompactDisc類型的bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Component public?class?CDPlayer?implements?MediaPlayer{ ??private?CompactDisc cd; ??@Autowired//這表明當Spring創(chuàng)建CDPlayer bean的時候,會通過這個構造器來進行實例化并且會傳入一個可設置給CompactDisc類型的bean. ??public?CDPlayer(CompactDisc cd){//構造器 ????this.cd = cd; ??} ??public?void?paly(){ ????cd.paly(); ??} }
@Autowired注解不僅能夠用在構造器上,還能用在屬性的Setter方法上.比如說,如果CDPlayer有一個setCompactDisc()方法,那么可以采用如下的注解形式進行自動裝配:
1 2 3 4 @Autowired public?void?setCompactDisc(CompactDisc cd){ ??this.cd = cd; }
但是如果沒有匹配的bean,那么在應用上下文創(chuàng)建的時候,Spring會拋出一個異常,為了避免異常的出現(xiàn),你可以將@Autowired的requied屬性設置為false
1 2 3 4 @Autowired(required=false) public?void?setCompactDisc(CompactDisc cd){ ??this.cd = cd; }
2|3三、通過Java代碼裝配bean
盡管在很多場景下通過組件掃描和自動裝配實現(xiàn)Spring的自動化掃描配置是更為推薦的方式,但在有些情況下自動化掃描的方案行不通,如想要將第三方庫中的組件裝配到自己的應用中。在這種情況下必須通過顯示 裝配的方式。
顯示裝配有兩種可選方案:Java和XML。JavaConfig是更好的方案:更強大、類型安全并對重構友好。因他就是Java代碼。
還是上面的那個案例,我們現(xiàn)在將它用Java代碼來實現(xiàn)裝配bean
接口:CompactDisc.java
1 2 3 4 5 package?soundsystem; public?interface?CompactDisc { ??void?play(); }
接口: MediaPlayer.java
1 2 3 4 5 package?soundsystem; public?interface?MediaPlayer { ??void?play(); }
CompactDisc的實現(xiàn)類:SgtPeppers.java
1 2 3 4 5 6 7 8 9 10 11 12 13 package?soundsystem; public?class?SgtPeppers?implements?CompactDisc { ??private?String title =?"曾經的你";? ??private?String artist =?"許巍"; ??@Override ??public?void?play() { ????System.out.println("Playing "?+ title +?" by "?+ artist); ??} }
細心的讀者可以發(fā)現(xiàn),這里我們去掉了@Compenent注解
MediaPlayer的實現(xiàn)類:CDPlayer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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實現(xiàn)注入
CDPlayerConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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); ??} }
區(qū)別與自動裝配,這里去掉了@ComponentScan注解,而是顯式的聲明了Bean。@Bean注解告訴了Spring上下文這個方法會將返回一個對象,該對象要注冊為Spring應用上下文中的bean,方法體重包含了最終產生bean實例的實現(xiàn)邏輯。
測試類:CDPlayerTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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(); ??} }
依然可以得出正確的輸出結果
2|4四、通過XML裝配bean
XML 配置是最原始最古老的 Bean 的裝配方案,曾經我們的項目離不開它,而如今,我們卻在慢慢的拋棄它,沒辦法,時代在進步,我們也要進步呀。為了能看懂前輩們寫的代碼,我們還是有必要來看一下如何通過 XML 來裝配 Bean。
創(chuàng)建一個Book類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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創(chuàng)建maven項目時會有一個resources文件夾)創(chuàng)建一個 beans.xml 文件,作為 Spring 的配置文件,然后在里邊配置一個 Book bean,如下:
1 2 3 4 5 6
在這里,我們在 class 屬性中配置類的全路徑,id 則表示 bean 的名稱,也可以通過 name 屬性來指定 bean 的名稱,大部分場景下兩者無任何差別,會有一些特殊場景下(例如用,隔開多個實例名,兩者的處理方案不同),兩者有區(qū)別。
接下來新建一個測試類,看一下我們的配置是否正確:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 的屬性值,前提是我們?yōu)?Book 類提供一個有參構造方法(大家在創(chuàng)建有參構造方法時,一定記得再順手加一個無參構造方法):
1 2 3 4 5 6 7 8 9 10 11 12 13 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 文件中,我們就可以通過構造方法注入相關值了:
1 2 3 4 5
使用構造方法注入相關值的時候,也可以使用下標來描述參數的順序,注意如果使用下標,參數順序不能錯:
1 2 3 4 5
注入成功之后,當我們再次去獲取 Bean 的時候,就可以看到這些屬性了。
2.通過屬性注入:
1 2 3 4 5
3|0Spring的面向切面(AOP)
3|1一、面向切面編程簡介
AOP(Aspect-Oriented Programming):面向切面的編程。OOP(Object-Oriented Programming)面向對象的編程。對于OOP我們已經再熟悉不過了,對于AOP,可能我們會覺得是一種新特性,其實AOP是對OOP的一種補充,OOP面向的是縱向編程,繼承、封裝、多態(tài)是其三大特性,而AOP是面向橫向的編程。
面向切面編程(AOP)通過提供另外一種思考程序結構的途經來彌補面向對象編程(OOP)的不足。在OOP中模塊化的關鍵單元是類(classes),而在AOP中模塊化的單元則是切面。切面能對關注點進行模塊化,例如橫切多個類型和對象的事務管理。
3|2二、AOP術語:
?與大多數技術一樣,AOP已經形成了自己的術語。描述切面的常用術語有通知(advice)、切點(pointcut)和連接點(join point)。初學者在學習AOP時最頭疼的就是理解這些概念,包括我自己也是這樣,網上面的一些博客在介紹AOP時也只是從定義角度去解釋,不太容易理解,下面我將用一個日常生活中的例子來向大家介紹AOP術語,方便大家理解。
我們現(xiàn)在每家每戶都需要用電,那么用電就會涉及到用電量和電費,在10年前科技還沒有如此發(fā)達的時候,我們的電費都是需要專門的人員來收取的,電力公司會安排人員去到不同的地區(qū)進行電表的查看和電費的收取。
通知(Advice):
當抄表員出現(xiàn)在我們家門口時,他們要登記用電量并回去向電力公司報告。顯然,他們必須有一份需要抄表的住戶清單,他們所匯報的信息也很重要,但記錄用電量才是抄表員的主要工作。類似的,切面也有目標——它必須要完成的工作。在AOP術語中,切面的工作被稱為通知。
Spring切面可以應用5種類型的通知:
前置通知(Before):在目標方法被調用之前調用通知功能;后置通知(After):在目標方法完成之后調用通知,此時不會關心方法的輸出是什么;返回通知(After-returning):在目標方法成功執(zhí)行之后調用通知;異常通知(After-throwing):在目標方法拋出異常后調用通知;環(huán)繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執(zhí)行自定義的行為。
連接點(Join Point):
電力公司為多個住戶提供服務,甚至可能是整個城市。每家都有一個電表,這些電表上的數字都需要讀取,因此每家都是抄表員的潛在目標。
同樣,我們的應用可能也有數以千計的時機應用通知。這些時機被稱為連接點。連接點是在應用執(zhí)行過程中能夠插入切面的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼可以利用這些點插入到應用的正常流程之中,并添加新的行為。
切點(Poincut):
如果讓一個工作人員去抄寫全市的所有電表,那么肯定是不現(xiàn)實的。實際上,電力公司會安排每一個抄表員負責一個地區(qū)的抄表工作,比如小宋負責陽光小區(qū)的抄表工作,小馬負責歐風小區(qū)的抄表工作。類似的,切點也是如此,一個程序中可能有很多連接點,那么并不是每個連接點我們都需要通知,我們只需要通知部分的連接點即可,那么切點就是這個我們通知的連接點。
切面(Aspect):
當抄表員開始一天的工作時,他知道自己要做的事情(報告用電量)和從哪些房屋收集信息。因此,他知道要完成工作所需要的一切東西。
切面是通知和切點的結合。通知和切點共同定義了切面的全部內容——它是什么,在何時和何處完成其功能。
3|3三、Spring對AOP的支持
1、動態(tài)代理:
Spring AOP構建在動態(tài)代理之上,也就是說,Spring運行時會為目標對象動態(tài)創(chuàng)建代理對象。代理類封裝了目標類,并攔截被通知方法的調用,再把調用轉發(fā)給真正的目標bean。當代理類攔截到方法調用時,在調用目標bean方法之前,會執(zhí)行切面邏輯。
??編輯
2、織入切面的時間:
通過在代理類中包裹切面,Spring在運行期把切面織入到Spring 管理的bean中,也就是說,直到應用需要被代理的bean時,Spring才會創(chuàng)建代理對象。
因為Spring運行時才創(chuàng)建代理對象,所以我們不需要特殊的編譯器來織入Spring AOP切面。
?
3|4四、Spring AOP的使用
假設我們有個現(xiàn)場表演的接口Performance和它的實現(xiàn)類SleepNoMore:
1 2 3 4 5 6 7 8 package?PerformTest; /** ?* 現(xiàn)場表演,如舞臺劇,電影,音樂會 ?*/ public?interface?Performance { ????void?perform(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package?PerformTest; import?org.springframework.stereotype.Component; /** ?* 戲劇:《哈姆雷特》 ?*/ @Component public?class?SleepNoMore?implements?Performance { ????@Override ????public?void?perform() { ????????System.out.println("戲劇《哈姆雷特》"); ????} }
既然是演出,就需要觀眾,假設我們的需求是:在看演出之前,觀眾先入座并將手機調整至靜音,在觀看演出之后觀眾鼓掌,如果演出失敗觀眾退票,我們當然可以把這些邏輯寫在上面的perform()方法中,但不推薦這么做,因為這些邏輯理論上和演出的核心無關,就算觀眾不將手機調整至靜音或者看完演出不鼓掌,都不影響演出的進行。
針對這個需求,我們可以使用AOP來實現(xiàn)。
1、定義切面:
定義一個觀眾的切面,并聲明前置、后置和異常通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 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:該注解用來定義前置通知,通知方法會在目標方法調用之前執(zhí)行
@After:該注解用來定義后置通知,通知方法會在目標方法調用之后執(zhí)行
@AfterReturning:該注解用來定義返回通知,通知方法會在目標方法返回后調用
@AfterThrowing:該注解用來定義異常通知,通知方法會在目標方法拋出異常后調用
execution(* PerformTest.Performance.perform(..))含義:
execution:在方法執(zhí)行時觸發(fā)
*:表明我們不關心方法返回值的類型,即可以是任意類型
.PerformTest.performance:使用全限定類名和方法名指定要添加前置通知的方法
(..):方法的參數列表使用(..),表明我們不關心方法的入參是什么,即可以是任意類型
2、定義配置類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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、定義測試類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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(); ????} }
運行結果:
1 2 3 4 5 Silencing cell phones Taking seats 戲劇《哈姆雷特》 perform finish CLAP CLAP CLAP!!!
從結果可以看出在戲劇《哈姆雷特》的前面和后面都有語句輸出,這就是Spring AOP的通知起了作用
4|0SpringMVC
4|1一、SpringMVC的執(zhí)行流程
?編輯
1、請求旅程的第一站是Spring的DispatcherServlet。與大多數基于Java的Web框架一樣,Spring MVC所有的請求都會通過一個前端控制器(front controller)Servlet。前端控制器是常用的Web應用程序模式,在這里一個單實例的Servlet將請求委托給應用程序的其他組件來執(zhí)行實際的處理。在SpringMVC中DispatcherServlet就是前端控制器。
2、Dispatcherservlet的任務是將請求發(fā)送給Spring MVC控制器(controller),控制器是一個用于處理請求的Spring組件。在典型的應用程序中可能會有多個控制器,Dispatcherservlet需要知道應該將請求發(fā)送給哪個控制器。所以DispatcherServlet以會查詢一個或多個處理器映射(handler mapping)來確定請求的下一站在哪里。處理器映射會根據請求所攜帶的URL信息來進行決策。
3、一旦選擇了合適的控制器,Dispatcherservlet會將請求發(fā)送給選中的控制器。到了控制器,請求會卸下其負載(用戶提交的信息)并耐心等待控制器處理這些信息。
4、控制器在完成邏輯處理后,通常會產生一些信息,這些信息需要返回給用戶并在瀏覽器上顯示。這些信息被稱為模型(model)。不過僅僅給用戶返回原始的信息是不夠的——這些信息需要以用戶友好的方式進行格式化,一般會是HTML。所以,信息需要發(fā)送給一個視圖(view),通常會是JSP??刂破魉龅淖詈笠患戮褪菍⒛P蛿祿虬⑶覙耸境鲇糜阡秩据敵龅囊晥D名。它接下來會將請求連同模型和視圖名發(fā)送回DispatcherServlete。
5、這樣,控制器就不會與特定的視圖相耦合,傳遞給DispatcherServlet的視圖名并不直接表示某個特定的JSP。實際上,它甚至并不能確定視圖就是JSP。相反,它僅僅傳遞了一個邏輯名稱,這個名字將會用來查找產生結果的真正視圖。Dispatcherservlet將會使用視圖解析器(view resolver)來將邏輯視圖名匹配為一個特定的視圖實現(xiàn),它可能是也可能不是JSP。
6、既然Dispatcherservlet已經知道由哪個視圖渲染結果,那請求的任務基本上也就完成了。它的最后一站是視圖的實現(xiàn)(可能是JSP)
7、在這里它交付模型數據。請求的任務就完成了。視圖將使用模型數據渲染輸出,這個輸出會通過響應對象傳遞給客戶端(不會像聽上去那樣硬編碼)
4|2二、SpingMVC初探
1、首先用maven創(chuàng)建一個web項目(我這里用的是idea),創(chuàng)建完成后的項目目錄結構如下:
?編輯
2、在pom.xml文件中引入spring相關jar包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
3、在web.xml中進行SpringMVC的相關配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
4、在resources目錄下新建springmvc-servlet.xml文件,如果沒有resources目錄的話自己新建一個resources目錄,并將其設置為Resources Root:
?編輯
springmvc-servlet.xml文件是Spring的配置文件,文件內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
5、在WEB-INF目錄下新建一個文件夾jsp,然后在jsp文件夾中創(chuàng)建一個hello.jsp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <%-- ??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"?%> ????
????????6、新建一個Controller類:
?編輯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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即可訪問
4|3?三、配置文件詳解
?1、web.xml配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
2、springmvc.xml文件的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 ????????????? ?????????????
?????
3、applicationContext-*.xml的配置:
?applicationContext-*.xml包括三個配置文件,分別對應數據層控制、業(yè)務邏輯service控制和事務的控制。
數據訪問層的控制,applicationContext-dao.xml的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
事務控制,applicationContext-transaction.xml的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
?4、?MyBatis的配置:
?SqlMapConfig.xml的配置 ??全局setting配置這里省略,數據庫連接池在spring整合文件中已經配置,具體setting配置參考官方文檔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
柚子快報邀請碼778899分享:數據庫 java
相關鏈接
本文內容根據網絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。