本篇是“《Spring3.X企業應用開發實戰》,陳雄華 林開雄著,電子工業出版社,2012.2出版”的學習筆記的第一篇,關于Spring最基礎的IoC和AOP。
在日常的開發中,最近幾年正在使用著Spring,過去使用過Spring.Net,從官方文檔及互聯網博客,看過很多Spring文章,出于各種原因,沒有系統的進行Spring的學習,這次通過這本書系統的學習了Spring框架,很多知識貫穿起來,改變了一些錯誤理解,受益匪淺。
**查看Spring源碼的方法:**
下載源碼后,執行import-into-eclipse.sh(bat),則會對源碼建立Eclipse工程,Eclipse導入即可,執行這個批處理,需要JDK7及以上版本的支持。耐心一點,時間較長
在閱讀Spring源碼的過程中,會需要很多JDK反射及注解的知識,有過小小總結,如下:[JDK框架簡析--java.lang包中的基礎類庫、基礎數據類型](http://blog.csdn.net/puma_dong/article/details/39670411)
另推薦一本書:《Spring Internals》Spring技術內幕,計文柯著,通過這本書,結合源代碼,對于深入理解Spring架構和設計原理很有幫助。
# 使用Spring的好處到底在哪里?
你得先體會無Spring是什么滋味,才能知道Spring有何好處;
POJO編程,輕量級,低侵入;
面向接口編程,DI,解耦,降低業務對象替換的復雜性;
以提高開發效率為目標,簡化第三方框架的使用方式;
靈活的基于核心 Spring 功能的 MVC 網頁應用程序框架。開發者通過策略接口將擁有對該框架的高度控制,因而該框架將適應于多種呈現(View)技術,例如 JSP,FreeMarker,Velocity,Tiles,iText 以及 POI。值得注意的是,Spring 中間層可以輕易地結合于任何基于 MVC 框架的網頁層,例如 Struts,WebWork,或 Tapestry;
**他的作者說:**
Spring是一個解決了許多在J2EE開發中常見的問題的強大框架。
Spring提供了管理業務對象的一致方法并且鼓勵了注入對接口編程而不是對類編程的良好習慣。
Spring的架構基礎是基于使用JavaBean屬性的Inversion of Control容器。然而,這僅僅是完整圖景中的一部分:Spring在使用IoC容器作為構建關注所有架構層的完整解決方案方面是獨一無二的。?
Spring提供了唯一的數據訪問抽象,包括簡單和有效率的JDBC框架,極大的改進了效率并且減少了可能的錯誤。Spring的數據訪問架構還集成了Hibernate和其他O/R mapping解決方案。?
Spring還提供了唯一的事務管理抽象,它能夠在各種底層事務管理技術,例如JTA或者JDBC之上提供一個一致的編程模型。?
Spring提供了一個用標準Java語言編寫的AOP框架,它給POJOs提供了聲明式的事務管理和其他企業事務--如果你需要--還能實現你自己的aspects。這個框架足夠強大,使得應用程序能夠拋開EJB的復雜性,同時享受著和傳統EJB相關的關鍵服務。?
Spring還提供了可以和總體的IoC容器集成的強大而靈活的MVC web框架。
# IoC
### 定義
IoC(控制反轉:Inverse of Control)是Spring容器的內核,AOP、聲明式事務等功能在此基礎上開花結果。
雖然IoC這個重要概念不容易理解,但它確實包含很多內涵,它涉及代碼解耦、設計模式、代碼優化等問題。
因為IoC概念的不容易理解,Martin Fowler提出了DI(依賴注入:Dependency Injection)的概念用來代替IoC,即讓調用類對某一接口實現類的依賴關系由第三方(容器或協作類)注入,以移除調用類對某一接口實現類的依賴。
Spring通過一個配置文件描述Bean和Bean之間的依賴關系,利用Java語言的反射功能實例化Bean并建立Bean之間的依賴關系。?
Spring的IoC容器在完成這些底層工作的基礎上,還提供了Bean實例緩存、生命周期管理、Bean實例代理、時間發布、資源裝載等高級服務。
### 初始化
### Bean工廠
#### 概述
com.springframework.beans.factory.BeanFactory,是Spring框架最核心的接口,它提供了高級IoC的配置機制;使管理不同類型的Java對象成為可能;是Spring框架的基礎設施,面向Spring本身。
#### 初始化
~~~
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource res = resolver.getResource("classpath:com/baobaotao/beanFactory/benas.xml");
//ClassPathResource res = new ClassPathResource("com/baobaotao/beanFactory/benas.xml");
BeanFactory bf = new XmlBeanFactory(res);
System.out.println("init BeanFactory");
Car car = bf.getBean("car",Car.class);
System.out.println("car bean is ready for use!");
~~~
XmlBeanFactory通過Resource裝載Spring配置信息并啟動IoC容器,然后就可以通過getBean方法從IoC容器獲取Bean了。
通過BeanFactory啟動IoC容器時,不會初始化配置文件中定義的Bean,初始化動作發生在第一個調用時 。
對于SingleTon的Bean來說,BeanFactory會緩存Bean。
**下面是一種更原始的編程式使用IoC容器的方法:**
~~~
//以下演示一種更原始的載入和注冊Bean過程,對我們了解IoC容器的工作原理很有幫助
//揭示了在IoC容器實現中的關鍵類,比如:Resource、DefaultListableBeanFactory、BeanDefinitionReader,之間的相互聯系,相互協作
ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
Car car = factory.getBean("car",Car.class);
System.out.println(car.toString());
~~~
**beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
>
<bean id="car" class="cl.an.Car"></bean>
</beans>
~~~
### ApplicationContext
#### 概述
com.springframework.context.ApplicationContext,建立在BeanFactory基礎上,提供了更多面向應用的功能,它提供了國際化支持和框架事件體系,更易于創建應用;面向使用Spring的開發者,幾乎所有的應用場合我們都直接使用ApplicationContext而非底層的BeanFactory
#### 初始化
~~~
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"conf/beans1.xml","conf/beans2.xml"});
Car car = ctx.getBean("car",Car.class);
~~~
ApplicationContext的初始化和BeanFactory有一個重大的區別:
后者在初始化容器時,并未實例化Bean,直到第一次訪問某個Bean時才實例目標Bean;而前者則在初始化應用上下文時就實例化所有單實例的Bean。因此ApplicationContext的初始化時間會比BeanFactory稍長一些,不過稍后的調用則沒有“第一次懲罰”的問題;
另一個最大的區別,前者會利用Java反射機制自動識別出配置文件中定義的BeanPostProcessor、InstantiationAwareBeanPostPrcecssor和BeanFactoryPostProcessor,并自動將他們注冊到應用上下文中;而后者需要在代碼中通過手工調用addBeanPostProcessor方法進行注冊。這也是為什么在應用開發時,我們普遍使用ApplicationContext而很少使用BeanFactory的原因之一。
可以在beans的屬性中定義default-lazy-init="true",達到延遲初始化的目的,這不能保證所有的bean都延遲初始化,因為有的bean可能被依賴導致初始化。不推薦延遲初始化。
### WebApplicationContext
#### 概述
WebApplicationContext是專門為Web應用準備的,它允許從相對于Web根目錄的路徑中裝載配置文件完成初始化工作。
從WebApplicationContext中可以獲得ServletContext的引用,整個Web應用上下文對象將作為屬性放置到ServletContext中,以便Web應用環境可以訪問Spring應用上下文。
#### 初始化
WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所區別,因為WebApplicationContext需要ServletContext實例,也即是說它必須在Web容器的前提下才能完成啟動的工作。有過Web開發經驗的讀者都知道可以在web.xml中配置自啟動的Servlet或定義Web容器監聽器,借助這兩者中的任何一個,我們就可以完成啟動Spring Web應用上下文的工作。
所有版本的Web容器都可以定義自啟動的Servlet,但只有Servlet2.3及以上版本的Web容器才支持Web容器監聽器。有些即使支持Servlet2.3的Web服務器,但也不能再Servlet初始化之前啟動Web監聽器,比如Weblogic 8.1,Websphere 5.x,Oracle OC4J 9.0。
Web.xml里面的配置節點如下:
~~~
<!--指定配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext.xml
</param-value>
</context-param>
<!--聲明Web容器監聽器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
~~~
### log4j
需要一種日志框架,我們使用Log4J,在類路徑下,提供Log4J配置文件log4j.xml,這樣啟動Spring容器才不會報錯。
對于WebApplicationContext,可以將Log4J配置文件放置在WEB-INF/classes下,這時Log4J引擎即可順利啟動。如果Log4J配置文件放置在其他位置,用戶還必須在web.xml指定Log4J配置文件位置。
Spring為啟動Log4J引擎提供了兩個類似于啟動WebApplicationContext的實現類:Log4jConfigServlet和Log4jConfigListener,不管采用哪種方式都必須保證能在在裝載Spring配置文件之前先裝載Log4J配置文件。
Web.xml里面的配置節點如下:
~~~
<!--指定Log4J配置文件位置-->
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.xml</param-value>
</context-param>
<!--聲明Log4J監聽器-->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
~~~
對于maven java application項目,log4j.xml直接放在src/main/resource下面即可,否則會報紅色警告:
~~~
二月 09, 2015 10:57:38 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@a6af6e: startup date [Mon Feb 09 22:57:38 CST 2015]; root of context hierarchy
二月 09, 2015 10:57:38 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
~~~
### 使用外部屬性文件
~~~
<!-- 定義受環境影響易變的變量 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<!-- 標準配置 -->
<value>classpath*:/config.properties</value>
<!-- 本地開發環境配置 -->
<value>file:/d:/conf/*.properties</value>
<!-- 服務器生產環境配置 -->
<value>file:/etc/conf/*.properties</value>
</list>
</property>
</bean>
~~~
在基于xml的配置方式中,通過${cas.server.url}的表達式即可訪問配置信息;在基于注解和基于Java類配置的Bean中,可以通過@Value("${cas.server.url}")的注解形式訪問配置信息。#是引用Bean的屬性值。
### 總結
BeanFactory、ApplicationContext和WebApplicationContext是Spring框架三個最核心的接口,框架中其他大部分的類都圍繞它們展開、為它們提供支持和服務。
在這些支持類中,Resource是一個不可忽視的重要接口,框架通過Resource實現了和具體資源的解耦,不論它們位于何種介質中,都可以通過相同的實例返回。
與Resource配合的另一個接口是ResourceLoader,ResourceLoader采用了策略模式,可以通過傳入資源的信息,自動選擇適合的底層資源實現類,為生產對資源的引用提供了極大的便利。
在一些非Web項目中,在入口函數(main)中,會顯示的初始化Spring容器,比如:ApplicationContext instance = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"}),這和Web項目中通過Listener(web.xml)初始化Spring容器,效果是一樣的。一個是手工加載,一個是通過web容器加載,對于Spring容器的初始化,效果是一樣的。
### Bean生命周期和作用域
Spring為Bean提供了細致周全的生命周期過程,通過實現特定的接口或通過<bean>屬性設置,都可以對Bean的生命周期過程施加影響,Bean的生命周期不但和其實現的接口相關,還與Bean的作用范圍有關。為了讓Bean綁定在Spring框架上,我們推薦使用配置方式而非接口方式進行Bean生命周期的控制。
在實際的開發過程中,我們很少控制Bean生命周期,而是把這個工作交給Spring,采用默認的方式。
Bean的作用域:singleton,prototype,request,session,globalSession,默認是singleton。
### 配置方式
基于Xml配置方式中,配置文件的3種格式:完整配置格式、簡化配置方式、使用p命名空間 。
基于注解配置方式中,使用到的注解符號:@Compoment,@Repository,@Service,@Controller,@Autowired(@Resource,@Inject),@Qualifier,@Scope,@PostConstruct,@PreDestroy ?。
基于Java類配置方式中,使用到的注解符號:@Configuration,@Bean 。
Bean不同配置方式比較,總結如下:
| 配置方式 | 基于XML配置 | 基于注解配置 | 基于Java類配置 |
|-----|-----|-----|-----|
| Bean定義 | 在XML文件中,通過<bean>元素定義Bean。如:<bean class="com.bbt.UserDao"/> | 在Bean實現類處通過標注@Component或衍生類(@Repository,@Service,@Controller)定義Bean | 在標注了Configuration的Java類中,通過在類方法上標注@Bean定義一個Bean。方法必須提供Bean的實例化邏輯。 |
| Bean名稱 | 通過<bean>的id或name屬性定義,如:<bean id=""userDao"" class=com.bbt.UserDao/>,默認名稱為:com.bbt.UserDao#0 | 通過注解的Value屬性定義,如@Component("userDao")。默認名稱為小寫字母打頭的類名(不帶包名):userDao | 通過@Bean的name屬性定義,如 @Bean("userDao"),默認名稱為方法名。 |
| Bean注入 | 通過<property>子元素或通過p命名空間的動態屬性,如p:userDao-ref="userDao"進行注入 | 通過在成員變量或方法入參處標注@Autowired,按類型匹配自動注入。還可以配合使用@Qualifier按名稱匹配方式呼入 | 比較靈活,可以通過在方法除通過@Autowired使方法入參綁定Bean,然后在方法中通過代碼進行注入,還可通過調研配置類的@Bean方法進行注入。 |
| Bean生命過程方法 | 通過<bena>的init-method和destroy-method屬性指定Bean實現類的方法名最多只能指定一個初始化方法和一個銷毀方法。 | 通過在目標方法上標注@PostConstruct和@PreDestroy注解指定初始化或銷毀方法,可以定義任意多個 | 通過@Bean的initMethod或destroyMethod指定一個初始化或銷毀方法;對于初始化方法來說,可以直接在方法內部通過代碼的方式靈活定義初始化邏輯。 |
| Bean作用范圍 | 通過<bean>的scope屬性指定,如:<bean class="com.bbt.UserDao" scope="prototype"> | 通過在類定義處標注@Scope指定,如:@Scope("prototype") | 通過在Bean方法定義處標注@Scope指定 |
| Bean延遲初始化 | 通過<bean>的lazy-init屬性綁定,默認為default,繼承于<beans>的default-lazy-init設置,該值默認為false | 通過在類定義處標注@Lazy指定,如@Lazy(true) | 通過在類定義處標注@Lazy指定 |
| 適合場景 | 1.Bean實現類來源于第三方類庫,如DataSource,JdbcTemplate等,因無法在類中標注注解,通過XML配置方式較好; 2.命名空間的配置,如aop,context等,只能采用基于XML的配置 | Bean的實現類是當前項目開發的,可以直接在Java類中使用基于注解的配置 | 基于Java類配置的優勢在于可以通過代碼方式控制Bean初始化的整體邏輯。所以如果實例化Bean的邏輯比較復雜,則比較適合用基于Java類配置的方式 |
一般采用XML配置DataSource,SessionFactory等資源Bean,在XML中利用aop,context命名空間進行相關主題的配置,其他的自己項目中開發的Bean,都通過基于注解配置的方式進行配置,即整個項目采用“基于XML+基于注解”的配置方式,很少采用基于Java類的配置方式。
### 通用知識點
1.Xml有5個特殊符號:<>&"',轉義字符分別為:< ?> ?& ?" ?' ?,也可以用<![CDATA[內容]]>的方式。
2.資源類型的地址前綴:class: class*: ?file: ?http:// ?ftp://
3.JavaBean規范規定:變量的前兩個字母要么全部大寫,要么全部小寫
4.默認構造函數是不帶參的構造函數。Java語言規定如果類中沒有定義任何構造函數,則JVM自動為其生成一個默認的構造函數。反之,如果類中顯式定義了構造函數,則JVM不會為其生成默認的構造函數。所以假設Car類中顯式定義了一個帶參的構造函數,如public Car(String brand),則需要同時提供一個默認構造函數public Car(),否則使用屬性注入時將拋出異常。
# AOP
AOP,Aspect Oriented Programming,面向切面編程
AOP的出現,是作為OOP的有益補充;AOP的應用場合是受限的,它一般只適合于那些具有橫切邏輯的應用場合:如性能監測、訪問控制、事務管理、日志記錄。
OOP是通過縱向繼承的機制,達到代碼重用的目的;AOP通過橫向抽取機制,把分散在各個業務邏輯中的相同代碼,抽取到一個獨立的模塊中,還業務邏輯類一個清新的世界;把這些橫切性的邏輯獨立出來很容易,但如何將這些橫切邏輯融合到業務邏輯中完成和原來一樣的業務操作,才是事情的關鍵,這也正是AOP要解決的主要問題。
"AOP術語:連接點(Joinpoint)、切點(Pointcut)、增強(Advice)、目標對象(Target)、引介(Introduction)、織入(Weaving)、代理(Proxy)、切面(Aspect);
AOP織入方式:編譯器織入、類裝載期織入、動態代理織入,Spring采用動態代理織入,AspectJ采用前兩種織入方式;"
Spring采用了兩種代理機制:基于JDK的動態代理和基于CGLib的動態代理;前者創建的代理對象性能差,后者創建對象花費時間長,都大約是10倍的差距,所以對于單例的代理對象或者具有實例池的代理對象,比較適合CGLib,反之適合JDK。
關于AOP技術的實現技術步驟,先不進行深入研究,其中JDK5.0開始的注解技術,是對開發效率有明顯改進的,可以深入了解下;實際所有基于注解的配置方式全部來源于JDK5.0注解技術的支持。
四種切面類型:@AspectJ、<aop:aspect>、Advisor、<aop:advisor>,JDK5.0以后全部采用@AspectJ方式吧。這四種切面定義方式,其底層實現實際是相同的,表象不同,本質歸一。
1、@AspectJ使用JDK5.0注解和正規的AspectJ的切點表達式語言描述切面,由于Spring只支持方法的連接點,所以Spring僅支持部分AspectJ的切點語言,這種方式是被Spring推薦的,需要在xml中配置的內容最少,演示:[http://blog.csdn.net/puma_dong/article/details/20863953#t28](http://blog.csdn.net/puma_dong/article/details/20863953#t28)
2、如果項目只能使用低版本的JDK,則可以考慮使用<aop:aspect>,這是基于Schema的配置方式
3、如果正在升級一個基于低版本Spring AOP開發的項目,則可以考慮使用<aop:advisor>復用已經存在的Advice類
4、基于Advisor類的方式,其實也是蠻自動的,在xml中配置一個Advisor節點,開啟自動創建代理就好了,比如:[http://blog.csdn.net/puma_dong/article/details/20863953#t27](http://blog.csdn.net/puma_dong/article/details/20863953#t27)
<!--開啟注解 -->
?<context:annotation-config />
參考:http://blog.sina.com.cn/s/blog_872758480100wtfh.html
?<!-- 開啟自動切面代理 -->
?<aop:aspectj-autoproxy />
聲明自動為spring容器中那些配置@aspectJ切面的bean創建代理,織入切面。當然,spring在內部依舊采用AnnotationAwareAspectJAutoProxyCreator進行自動代理的創建工作,但具體實現的細節已經被<aop:aspectj-autoproxy />隱藏起來了
<aop:aspectj-autoproxy />有一個proxy-target-class屬性,默認為false,表示使用jdk動態代理織入增強,當配為<aop:aspectj-autoproxy ?proxy-target-class="true"/>時,表示使用CGLib動態代理技術織入增強。不過即使proxy-target-class設置為false,如果目標類沒有聲明接口,則spring將自動使用CGLib動態代理;關于這點,很多資料這么多,但我實際的測試是,如果沒有接口,只能有cglib,否則會報異常“Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'person' must be of type [cl.an.Person], but was actually of type [cl.an.$Proxy9]”。
### 演示JDK動態代理實現AOP的基本原理
~~~
import java.lang.reflect.*;
class AOPTest {
public static void main(String[] args) throws Exception {
HelloInterface hello = BeanFactory.getBean("HelloImpl",HelloInterface.class);
hello.setInfo("zhangsan","zhangsan@163.com");
}
}
interface HelloInterface {
public String setInfo(String name,String email);
}
class HelloImpl implements HelloInterface {
private String name;
private String email;
@Override
public String setInfo(String name,String email) {
this.name = name;
this.email = email;
System.out.println("\n\n===>setInfo函數內部輸出此行...");
return "OK";
}
}
class AOPHandler implements InvocationHandler {
private Object target;
public AOPHandler(Object target) {
this.target = target;
}
public void println(String str,Object... args) {
System.out.println(str);
if(args == null) {
System.out.println("\t\t\t 未傳入任何值...");
} else {
for(Object obj:args) {
System.out.println("\t\t\t" + obj);
}
}
}
@Override
public Object invoke(Object proxyed,Method method,Object[] args)
throws IllegalArgumentException,IllegalAccessException,InvocationTargetException
{
//以下定義調用之前執行的操作
System.out.println("\n\n===>調用方法名: " + method.getName());
Class<?>[] variables = method.getParameterTypes();
System.out.println("\n\t參數類型列表:\n");
for(Class<?> typevariable : variables) {
System.out.println("\t\t\t" + typevariable.getName());
}
println("\n\n\t傳入參數值為:",args);
//以下開始執行代理方法
System.out.println("\n\n開始執行method.invoke...調用代理方法...");
Object result = method.invoke(target,args);
//以下定義調用之后執行的操作
println("\n\n\t返回的參數為:",result);
println("\n\n\t返回值類型為:",method.getReturnType());
return result;
}
}
class BeanFactory {
public static Object getBean(String className) throws InstantiationException,IllegalAccessException,ClassNotFoundException {
Object obj = Class.forName(className).newInstance();
InvocationHandler handler = new AOPHandler(obj);//定義過濾器
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
@SuppressWarnings("unchecked")
public static<T> T getBean(String className,Class<T> c) throws InstantiationException,IllegalAccessException,ClassNotFoundException {
return (T)getBean(className);
}
}
~~~
其他參考方案:http://javatar.iteye.com/blog/814426/
### 演示Cglib動態代理實現AOP的基本原理
對于沒有通過接口定義業務方法的類,如何動態創建代理實例呢?JDK的代理技術顯然已經黔驢技窮,CGLib作為一個替代者,填補了這個空缺。
CGLib采用非常底層的字節碼技術,可以為一個類創建子類,并在子類中采用方法攔截的技術攔截所有父類的方法調用,并順勢織入橫切邏輯。
以下會有代碼演示,關于代碼演示,最簡單的就是創建一個maven項目,依賴cglib后,會自動找出相關依賴。
本例采用較笨的辦法,先把cglib需要的jar下載下來cglib-3.1.jar和asm-4.2.jar,然后在記事本編寫TestForumService.java,之后使用javac編譯,java運行。
javac -classpath cglib-3.1.jar TestForumService.java
java -classpath .;cglib-3.1.jar;asm-4.2.jar TestForumService,對于windows使用;連接,對于linux,使用:連接。
~~~
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class TestForumService
{
public static void main(String[] args) throws Exception {
CglibProxy proxy = new CglibProxy();
ForumServiceImpl forumService = (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);
forumService.removeTopic(100);
}
}
class CglibProxy implements MethodInterceptor
{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create(); //通過字節碼技術動態創建子類實例
}
//攔截父類所有方法的調用
public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy)
throws Throwable {
PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName());
Object result = proxy.invokeSuper(obj, args);
PerformanceMonitor.end();
return result;
}
}
//將要被注入切面功能的類
class ForumServiceImpl
{
public void removeTopic(int topicId) throws Exception {
System.out.println("模擬刪除Topic記錄:" + topicId);
//Thread.sleep(20);
}
}
//性能監視類(切面類)
class PerformanceMonitor
{
private static ThreadLocal<MethodPerformance> performanceRecord =
new ThreadLocal<MethodPerformance>();
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformance mp = new MethodPerformance(method);
performanceRecord.set(mp);
}
public static void end() {
System.out.println("end monitor...");
MethodPerformance mp = performanceRecord.get();
mp.printPerformance();
}
}
class MethodPerformance
{
private long begin,end;
private String serviceMethod;
public MethodPerformance(String serviceMethod) {
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis();
}
public void printPerformance() {
end = System.currentTimeMillis();
long elapse = end - begin;
System.out.println(serviceMethod + "花費" + elapse + "毫秒。");
}
}
~~~
### 演示Spring項目的自定義注解
這個例子演示的是,SpringAOP通過自動代理(BeanPostProcessor)和切面(Advisor)來完成環繞增強(通過實現AOP聯盟定義的MethodInterceptor接口)的過程,這個過程中,也會讀取和分析注解,所以也是一個注解解析器,java代碼如下:
~~~
package cl.an;
import java.lang.annotation.*;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
public class MainTest {
public static void main(String[] args) {
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
//測試IOC
MainUtil mainUtil = ctx.getBean("mainUtil",MainUtil.class);
if(mainUtil == null) {
System.out.println("null");
} else {
System.out.println(mainUtil.getString());
}
//測試自定義注解
Person person = ctx.getBean("person",Person.class);
System.out.println(person.say());
}
}
@Service
class MainUtil {
@Autowired
private MainConfigUtil configUtil;
public String getString() {
return "MainUtil." + configUtil.getString();
}
}
@Service
class MainConfigUtil {
public String getString() {
return "MainConfigUtil";
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface AnnotationTest {
String value();
}
@Component("person")
class Person {
@AnnotationTest("Annotation's Content")
public String say(){
return "I'm OK";
}
}
@Component("annotationTestAdvice")
class AnnotationTestAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
if(invocation.getMethod().isAnnotationPresent(AnnotationTest.class)){
String content = null;
Annotation annotation = invocation.getMethod().getAnnotation(AnnotationTest.class);
if(annotation!=null){
content = ((AnnotationTest)annotation).value();
System.out.println("獲取到注解的內容:" + content);
}
System.out.println("方法調用之前要進行的工作");
Object o = invocation.proceed();
System.out.println("方法調用之后要進行的工作");
return o;
}else{
return invocation.proceed();
}
}
}
~~~
applicationContext.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:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"
default-lazy-init="true">
<description>Spring公共配置</description>
<!--開啟注解 -->
<context:annotation-config />
<!-- 開啟自動切面代理(AnnotationAwareAspectJAutoProxyCreator) -->
<!-- true:代表使用cglib進行動態代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 開啟自動切面代理(DefaultAdvisorAutoProxyCreator) -->
<!-- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> -->
<context:component-scan base-package="cl.an">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<bean id="nameMatchMethodPointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="annotationTestAdvice"/>
<property name="mappedNames">
<list>
<value>say</value>
</list>
</property>
</bean>
</beans>
~~~
pom.xml
~~~
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cl</groupId>
<artifactId>an</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>an</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.0.6.RELEASE</spring.version>
</properties>
<dependencies>
<!-- logging -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<!-- Spring framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
~~~
**還可以只通過注解方式實現注解:**
~~~
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
~~~
~~~
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MonitorLog {
public String MonitorKey() default "";
public long TimeOut() default 200;//單位毫秒
}
~~~
~~~
@Aspect
public class MonitorLogAnnotationProcessor {
@Around("execution(* *(..)) && @annotation(log)")
public Object aroundMethod(ProceedingJoinPoint pjd ,MonitorLog log) {
Object result = null;
String monitorKey = log.MonitorKey();
long timeout = log.TimeOut();
String e_monitorKey="";
String monitorKey_timeout = "";
if(monitorKey==null || "".equals(monitorKey)){
MethodSignature signature = (MethodSignature) pjd.getSignature();
Method method = signature.getMethod();
String mName = method.getName();
Class<?> cls = method.getDeclaringClass() ;
String cName = cls.getName() ;
monitorKey= cName+"."+mName ;
}
e_monitorKey= monitorKey+".error" ;
monitorKey_timeout = monitorKey+".timeout" ;
long start = System.currentTimeMillis();
try {
result = pjd.proceed();
} catch (Throwable e) {//todo 是否捕獲所有異常
JMonitor.add(e_monitorKey);
}finally {
long elapseTime = System.currentTimeMillis() - start;
if(elapseTime > timeout) {
JMonitor.add(monitorKey_timeout);
}
JMonitor.add(monitorKey, elapseTime);
}
return result;
}
}
~~~
### SpringAOP實現方式從低級到高級
這個例子通過一個“前置增強”的逐步簡化步驟,演示SpringAOP如何從最低級的實現,到最高級(最簡化、最自動)的實現的進化過程。
### 純粹代碼方式(ProxyFactory)
~~~
package cl.an.advice;
import java.lang.reflect.Method;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class TestBeforeAdvice {
public static void main(String[] args) {
Waiter target = new NaiveWaiter();
BeforeAdvice advice = new GreetingBeforeAdvice();
//Spring提供的代理工廠
ProxyFactory pf = new ProxyFactory();
//設置代理目標
pf.setTarget(target);
//為代理目標添加增強
pf.addAdvice(advice);
//生成代理實例
Waiter proxy = (Waiter)pf.getProxy();
proxy.greeTo("John");
proxy.serveTo("Tom");
}
}
interface Waiter {
void greeTo(String name);
void serveTo(String name);
}
class NaiveWaiter implements Waiter {
public void greeTo(String name) {
System.out.println("greet to " + name + "...");
}
public void serveTo(String name) {
System.out.println("serving " + name + "...");
}
}
class GreetingBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object obj)
throws Throwable {
String clientName = (String)args[0];
System.out.println("How are you! Mr." + clientName + ".");
}
}
~~~
ProxyFactory代理工廠將GreetingBeforeAdvice的增強織入到目標類NaiveWaiter中。在ProxyFactory內部,依然使用的是JDK代理或CGLib代理,將增強應用到目標類中。
Spring定義了org.springframework.aop.framework.AopProxy接口,并提供了兩個實現類,Cglib2AopProxy和JdkDynamicAopProxy。
### Spring配置的方式(ProxyFactoryBean)
我們通過Spring配置的方式,代替編寫ProxyFactory相關的代碼。
1.beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
>
<bean id="greetingAdvice" class="cl.an.advice.GreetingBeforeAdvice"/>
<bean id="target" class="cl.an.advice.NaiveWaiter"></bean>
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="cl.an.advice.Waiter"
p:interceptorNames="greetingAdvice"
p:target-ref="target"></bean>
</beans>
~~~
2.調用代碼簡化為:
~~~
public class TestBeforeAdvice {
public static void main(String[] args) {
String configPath = "cl/an/advice/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greeTo("John");
waiter.serveTo("Tom");
}
}
~~~
### 切面(Advisor)方式
本例子中演示的是正則表達式切面,并且限制了只對greeTo方法進行增強。
1.beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
>
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="greetingAdvice">
<property name="patterns">
<list>
<!-- 用正則表達式,只對greeTo方法進行增強 -->
<value>.*greeT.*</value>
</list>
</property>
</bean>
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="regexpAdvisor"
p:target-ref="target"
p:proxyTargetClass="true">
</bean>
<bean id="greetingAdvice" class="cl.an.advice.GreetingBeforeAdvice"/>
<bean id="target" class="cl.an.advice.NaiveWaiter"></bean>
</beans>
~~~
### 自動創建代理
以上,我們通過ProxyFactoryBean創建織入切面的代理,對于小型系統,可以將就使用,但對擁有眾多需要代理Bean的系統系統,需要做的配置工作就太多太多了。
但是,Spring為我們提供了自動代理機制,讓容器為我們自動生成代理,把我們從繁瑣的配置工作中解放出來。
在內部,Spring使用BeanPostProcessor自動完成這項工作。
我們可以使用BeanNameAutoProxyCreator,只對某些符合通配符的類進行增強,也可以使用DefaultAdvisorAutoProxyCreator,自動掃描容器中的Advisor,并將Advisor自動織入到匹配的目標Bean中,即為匹配的目標Bean自動創建代理。
1.beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
>
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="greetingAdvice">
<property name="patterns">
<list>
<!-- 用正則表達式,只對greeTo方法進行增強 -->
<value>.*greeT.*</value>
</list>
</property>
</bean>
<bean id="waiter" class="cl.an.advice.NaiveWaiter"/>
<bean id="greetingAdvice" class="cl.an.advice.GreetingBeforeAdvice"/>
<!-- 開啟自動切面代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>
~~~
### @AspectJ定義切面的方式
這是一種配置最少的切面定義方式,功能強大,但是需要學習AspectJ切點表達式語言。
1.代碼內容
~~~
package cl.an.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AspectJProxyTest {
public static void main(String[] args) {
String configPath = "cl/an/aspectj/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greeTo("John");
waiter.serveTo("Tom");
}
}
interface Waiter {
void greeTo(String name);
void serveTo(String name);
}
class NaiveWaiter implements Waiter {
public void greeTo(String name) {
System.out.println("greet to " + name + "...");
}
public void serveTo(String name) {
System.out.println("serving " + name + "...");
}
}
@Aspect
class PreGreetingAspect {
@Before("execution(* greeTo(..))")
public void beforeGreeting() {
System.out.println("How are you");
}
}
~~~
2.beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"
>
<!-- 開啟自動切面代理:基于AspectJ切面的驅動器 -->
<!-- true:代表使用cglib進行動態代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean id="waiter" class="cl.an.aspectj.NaiveWaiter"/>
<bean id="greetingAdvice" class="cl.an.aspectj.PreGreetingAspect"/>
</beans>
~~~
### LoadTimeWeaver-LTW
Spring也支持類加載期的織入,在類加載期,通過字節碼編輯的技術,將切面織入到目標類中,這種織入方式成為LTW(Load Time Weaving)。就像運行期織入,其底層依靠的是JDK動態代理和CGLib字節碼增強一樣,類加載器的注入,底層依靠的也是JDK,jang.lang.instrument包中的兩個接口(ClasFileTransformer和Instrumentation)。
1.代碼
~~~
package instrument;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class Test {
public static void main(String[] args) {
System.out.println("I'm in main() of Test...");
}
}
class Transformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("Hello " + className + "!");
return null;
}
}
//這個代理類,會在程序入口main()方法執行前執行,執行的是premain()方法
class Agent {
//這個Agent代理類,必須按一下前面方式定義premain()
public static void premain(String agentArgs,Instrumentation inst) {
ClassFileTransformer t = new Transformer();
inst.addTransformer(t);
}
}
~~~
2.MANIFEST.MF內容,這個文件在制作jar時用到,會導出到META-INF目錄下的MANIFEST.MF
~~~
Manifest-Version: 1.0
Premain-Class: instrument.Agent
~~~
注意,這個文件格式要求嚴格,Premain-Class:后面必須有個空格,文件后面必須至少兩行空行。
Eclipse導出jar把,然后執行java -javaagent:test.jar instrument.Test,可以看到Agent的代碼內容已經被織入了。
關于Spring LTW,利用的是META-INF目錄下的aop.xml,可以利用AspectJ表達式語言進行更多的控制,和前面解釋AspectJ的部分差不多。
# 總結
關于Spring,我個人的理解是:IoC是基礎,然后其他一切帶給我們編程簡便性的地方,全部來源于AOP,讓這種橫向抽取機制,封裝常用操作。比如:
加上@Transactional標記,就對方法或者類開啟了事務;
加上@Cacheable標記,就對方法開啟了緩存。
- 前言
- Java之旅--如何從草根成為技術專家
- 《深入理解Java虛擬機》學習筆記
- 《Spring3.X企業應用開發實戰》學習筆記--IoC和AOP
- 《Tomcat權威指南》第二版學習筆記
- Java之旅--多線程進階
- Java之旅--Web.xml解析
- 《Spring3.X企業應用開發實戰》學習筆記--DAO和事務
- 《Spring3.X企業應用開發實戰》學習筆記--SpringMVC
- Java之旅--定時任務(Timer、Quartz、Spring、LinuxCron)
- Spring實用功能--Profile、WebService、緩存、消息、ORM
- JDK框架簡析--java.lang包中的基礎類庫、基礎數據類型
- JDK框架簡析--java.util包中的工具類庫
- JDK框架簡析--java.io包中的輸入輸出類庫
- Java之旅--通訊
- Java之旅--XML/JSON
- Java之旅--Linux&amp;java進階(看清操作系統層面的事)
- Java之旅--硬件和Java并發(神之本源)
- Java之旅--設計模式
- jetty