接上篇《JAVA WEB快速入門之從編寫一個JSP WEB網站了解JSP WEB網站的基本結構、調試、部署》,通過一個簡單的JSP WEB網站了解了JAVA WEB相關的知識,比如:Servlet、Fitler、Listner等,這為后面搭建基於SSM的框架奠定了基礎知識,當然光了解JSP相關的知識還不行,我們還得了解掌據Spring相關的知識,因為SSM,是基於Spring框架(SpringMVC)搭建的,好了廢話不多說,直接進入主題。
什么是Spring?
Spring是一個開放源代碼的設計層面框架,他解決的是業務邏輯層和其他各層的松耦合問題,因此它將面向接口的編程思想貫穿整個系統應用...詳見百度百科:https://baike.baidu.com/item/spring/85061
核心模塊如下圖示:(來源網絡)
依賴關系:(來源網絡)
一、建立一個Spring項目:
1.1打開eclipse,依次操作:File->New->Java Project,然后設置一些必要的項目屬性(類似操作在上一篇),最后finish創建完成一個空的JAVA Project,注意目前並沒有Spring環境。如下圖示:
1.2下載Spring相關的JAR包(下載地址:http://repo.spring.io/release/org/springframework/spring/ 或使用MAVAN的下載地址:http://maven.springframework.org/release/org/springframework/spring/)
打開下載頁面后,從列表中找到最新的一個地址,如目前的最新版本:(5.1.2.RELEASE)
通過Spring官網也能看到當前顯示的最新版本(官網地址:https://spring.io/projects/spring-framework#learn)
1.3點擊進入選擇的下載版本鏈接,然后點擊如下圖示的地址下載Spring JAR包:
1.4下載后解壓,然后在JAVA項目中引入剛才下載的Spring JAR包(在解壓后的libs目錄下),引入方式與上篇介紹基本相同,通過項目右擊:Buid path->Configure Buid Path->切換到Libraries頁簽->Add External JARs(即:添加外部JAR包),如下圖示:
導入到項目后的效果如下圖示:
當然除了引入Spring相關JAR包外,應該還需要導入一個Commons Logging JAR包,因為Spring-core 包有依賴此包,故我們也應該下載並引入(地址:http://commons.apache.org/proper/commons-logging/download_logging.cgi),下載頁面如下圖示:
導入方法同上面導入Spring JAR包操作相同,故不再重述。
到目前為止一個Spring的項目環境已經搭建好了,有點感覺像在VS中創建一個空的WEB項目,然后引入相關的DLL最后形成一個MVC或WEB API框架。
二、 使用Spring的依賴注入功能
2.1 了解依賴注入必要知識:
Srping IOC容器:是 Spring 框架的核心。容器將創建對象並把它們連接在一起,配置它們,並管理他們的整個生命周期從創建到銷毀。Spring 容器使用依賴注入(DI)來管理組成一個應用程序的組件(這些對象被稱為 Spring Beans)。通過閱讀配置元數據提供的指令,容器知道對哪些對象進行實例化,配置和組裝。配置元數據可以通過 XML,Java 注解或 Java 代碼來表示。IOC 容器負責實例化、定位、配置應用程序中的對象及建立這些對象間的依賴。通常new一個實例,控制權由程序員控制,而"控制反轉"是指new實例工作不由程序員來做而是交給Spring容器來做。簡容容器對象接口:BeanFactory(常用實現類:XmlBeanFactory)、高級容器對象接口:ApplicationContext(常用實現類:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、WebXmlApplicationContext)
Spring Bean:所有可以被spring容器實例化並管理的java類都可以稱為SpringBean
POJO、Java Bean、Spring Bean區別:
POJO是一個簡單的、普通Java對象,特點是有private的屬性(在C#中稱為字段)和public的getter、setter方法,除此之外不具有任何特殊角色,不繼承或不實現任何其它Java框架的類或接口。一般用於數據的傳輸,比如作為DTO對象;
JavaBean 是一種JAVA語言寫成的可重用組件。JavaBean符合一定規范編寫的Java類,不是一種技術,而是一種規范。它的方法命名,構造及行為必須符合特定的約定:
A.所有屬性為private。B.類必須具有一個公共的(public)無參構造函數,C.private屬性必須提供public的getter和setter來給外部訪問,並且方法的命名也必須遵循一定的命名規范。 D.這個類應是可序列化的,要實現serializable接口。
當一個POJO可序列化,有一個無參的構造函數,使用getter和setter方法來訪問屬性時,他就是一個JavaBean,而Spring Bean,不需要像JavaBean 一樣遵循一些規范(不過對於通過設值方法注入的Bean,一定要提供setter 方法。)
2.2在項目根目錄下創建Beans.xml文件(Spring Bean配置文件),操作步驟:src右鍵->New->Other->搜索xml->選擇xml file->按默認步驟操作直至完成即可,創建完的XML文件可能只有如下內容:
<?xml version="1.0" encoding="UTF-8"?>
這時我們需要手動添加必要的Spring Bean的命名空間(xmlns),添加后的完整的Spring 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-4.3.xsd"> </beans>
其中http://www.springframework.org/schema/beans/spring-beans-4.3.xsd這個4.3是目前最新的版本,可以根據http://www.springframework.org/schema/beans獲得最新的XSD文件
2.3定義一個Bean,並配置到Bean配置文件中(beans.xml),同時使用ClassPathXmlApplicationContext IOC容器來獲得實例,代碼如下:
package cn.zuowenjun.java; public class FirstBean { private String uuidStr = java.util.UUID.randomUUID().toString(); private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public void showMessage(String name) { System.out.printf("hello,%1$s,Message:%2$s UUID:%3$s %n", name, message, uuidStr); } public void throwEx() throws Exception { throw new Exception("throw a new Exception!"); } public void init() { System.out.println(uuidStr + ":init..."); } public void destroy() { System.out.println("destroy..."); } } package cn.zuowenjun.java; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringDemoApp { public static void main(String[] args) { // TODO Auto-generated method stub ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("Beans.xml"); context.start(); FirstBean firstBean= (FirstBean)context.getBean("firstBean"); firstBean.showMessage("夢在旅途"); context.close(); context.registerShutdownHook(); } }
在Beans.xml中注冊FirstBean(省略XML聲明定義的固定部份,僅貼出Bean的定義)
<bean id="firstBean" class="cn.zuowenjun.java.FirstBean" init-method="init" destroy-method="destroy" scope="singleton"> <property name="message" value="i love Spring!"></property> </bean>
運行SpringDemoApp即可看到輸出的結果:
配置說明:
bean元素:表示注冊一個bean;
id:bean的唯一識別名稱(IOC容器getBean就是取這個名稱);
class:bean的完整類名(包含包名);
init-method:定義bean初始化完成后回調的方法;(也可以通過將Bean實現InitializingBean接口,重寫afterPropertiesSet方法)
destroy-method:定義當包含該 bean 的容器被銷毀時回調方法;(也可以通過將Bean實現DisposableBean接口,重寫destroy方法)
注:如果你有太多具有相同名稱的初始化或者銷毀回調方法的 Bean,那么你不需要在每一個 bean 上聲明初始化方法和銷毀方法,直接在Bean元素中配置 default-init-method 和 default-destroy-method 屬性即可
scope:定義bean的作用域,生命周期范圍,具體值如下圖示:(來源網絡)
property元素:表示Bean的屬性
當然還有其它屬性,我們稍后示例中有用到的時候再補充說明。
注:可以通過定義一個類並實現BeanPostProcessor接口(稱為:Bean 后置處理器),實現在調用初始化方法前后對 Bean 進行額外的處理
2.4分別再定義SecondBean、ThirdBean類,演示通過構造函數注入、屬性注入,實現代碼如下:
package cn.zuowenjun.java; import java.util.List; public class SecondBean { private int intProp; private String strProp; private ThirdBean thirdBean; private FirstBean firstBean=null; public SecondBean(int ipro,String sPro,FirstBean frtBean) { this.intProp=ipro; this.strProp=sPro; this.firstBean=frtBean; } public int getIntProp() { return intProp; } public void setIntProp(int intProp) { this.intProp = intProp; } public String getStrProp() { return strProp; } public void setStrProp(String strProp) { this.strProp = strProp; } public ThirdBean getThirdBean() { return thirdBean; } public void setThirdBean(ThirdBean thirdBean) { this.thirdBean = thirdBean; } public void outPutAll() { System.out.println("output start>>>>"); System.out.printf("intProp:%d,strProp:%s %n",intProp,strProp); firstBean.showMessage(strProp); List<Integer> list=thirdBean.getListProp(); StringBuffer strBuffer=new StringBuffer(); for(Integer i:list) { strBuffer.append(i.toString()+","); } System.out.println(strBuffer.toString()); System.out.println("output end<<<<"); } } package cn.zuowenjun.java; import java.util.*; public class ThirdBean { private List<Integer> listProp; public List<Integer> getListProp() { return listProp; } public void setListProp(List<Integer> listProp) { this.listProp = listProp; } }
配置注冊相關Bean:
<bean id="firstBean" class="cn.zuowenjun.java.FirstBean" init-method="init" destroy-method="destroy" scope="singleton"> <property name="message" value="i love Spring!"></property> </bean> <bean id="secondBean" class="cn.zuowenjun.java.SecondBean"> <constructor-arg type="int" value="520"></constructor-arg> <constructor-arg type="java.lang.String" value="JAVAER"></constructor-arg> <constructor-arg name="frtBean" ref="firstBean"></constructor-arg> <property name="thirdBean" ref="thirdBean"></property> </bean> <bean id="thirdBean" class="cn.zuowenjun.java.ThirdBean"> <property name="listProp"> <list> <value>1</value> <value>2</value> <value>3</value> <value>4</value> <value>5</value> <value>6</value> <value>7</value> <value>8</value> <value>9</value> </list> </property> </bean>
配置補充說明:
constructor-arg元素:表示構造函數參數,type:表示參數類型,name:表示參數名,value:表示參數注入的值(一般常用於基礎類型及字符串),ref:表示參數注入的依賴Bean的ID;
property下的list元素表示的是注入這個屬性的值為list集合,按照list集合方式配置集合中的每個值,當然除了list還有其它的集合注入方式,可參見:https://www.w3cschool.cn/wkspring/kp5i1ico.html
在constructor-arg元素使用ref可以實現構造函數注入指定依賴的Bean,property元素使用ref可以實現屬性setter方法注入指定依賴的Bean
從上面的配置來看,構造函數注入可以根據name、type來實現自動匹配完成依賴注入,還支持根據參數個數索引來實現自動匹配完成依賴注入,如下所示:
<bean id="secondBean" class="cn.zuowenjun.java.SecondBean"> <constructor-arg index="0" value="520"></constructor-arg> <constructor-arg index="1" value="JAVAER"></constructor-arg> <constructor-arg index="2" ref="firstBean"></constructor-arg> <property name="thirdBean" ref="thirdBean"></property> </bean>
除了在XML中顯式的配置 constructor-arg、property元素來指定注入,我們還可以通過Bean的自動裝配功能實現自動根據構造函數或屬性的參數名(byName)、類型(byType)自動匹配注入依賴項,具體做法是:
在定義Bean元素時,autowire屬性設置為:byName或byType或constructor或autodetect(優先嘗試通過構造函數參數類型匹配,若不行則按照byType),同時對應的constructor-arg、property元素可省略不匹配;(注意要符合自動裝配要求)
最后在main方法加入如下代碼然后運行:
SecondBean secondBean=(SecondBean)context.getBean("secondBean"); secondBean.outPutAll();
輸出結果如下:
2.5通過JAVA注解來配置依賴注入,這樣Bean配置文件可以少些配置,要實現注解配置依賴注入首先需要在Bean配置文件的根元素(beans)添加context命名空間,然后去掉相關的依賴注入的元素節點,最后改造完成的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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <context:annotation-config /> <bean id="firstBean" class="cn.zuowenjun.java.FirstBean" init-method="init" destroy-method="destroy" scope="singleton"> <property name="message" value="i love Spring!"></property> </bean> <bean id="firstBean2" class="cn.zuowenjun.java.FirstBean" init-method="init" destroy-method="destroy" scope="singleton"> <property name="message" value="i love Spring -2!"></property> </bean> <bean id="secondBean" class="cn.zuowenjun.java.SecondBean"> <constructor-arg type="int" value="520"></constructor-arg> <constructor-arg type="java.lang.String" value="JAVAER"></constructor-arg> </bean> <bean id="thirdBean" class="cn.zuowenjun.java.ThirdBean"> <property name="listProp"> <list> <value>1</value> <value>2</value> <value>3</value> <value>4</value> <value>5</value> <value>6</value> <value>7</value> <value>8</value> <value>9</value> </list> </property> </bean> </beans>
配置變化點:增加了context命名空間及對應的schemaLocation信息,然后添加了<context:annotation-config />元素,該元素告訴Spring IOC容器采要注解配置,最后移除了有關之前使用ref的依賴注入的元素,改為在代碼中通過如下一系列注解來實現:
注意:@Required已被廢棄,使用@Autowired(required=true)替代,有些網上教程還是舊的。
代碼改變部份(主要是給FirstBean增加@PostConstruct、@PreDestroy注解,用於指示初始回調、銷毀回調方法,給SecondBean增加@Autowired(required=true)、@Qualifier注解):
package cn.zuowenjun.java; import javax.annotation.*; public class FirstBean { private String uuidStr = java.util.UUID.randomUUID().toString(); private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public void showMessage(String name) { System.out.printf("hello,%1$s,Message:%2$s UUID:%3$s %n", name, message, uuidStr); } public void throwEx() throws Exception { throw new Exception("throw a new Exception!"); } @PostConstruct public void init() { System.out.println(uuidStr + ":init..."); } @PreDestroy public void destroy() { System.out.println("destroy..."); } } package cn.zuowenjun.java; import java.util.List; import org.springframework.beans.factory.annotation.*; public class SecondBean { private int intProp; private String strProp; private ThirdBean thirdBean; private FirstBean firstBean=null; @Autowired(required=true) public SecondBean(int ipro,String sPro,@Qualifier("firstBean2") FirstBean frtBean) { this.intProp=ipro; this.strProp=sPro; this.firstBean=frtBean; } public int getIntProp() { return intProp; } public void setIntProp(int intProp) { this.intProp = intProp; } public String getStrProp() { return strProp; } public void setStrProp(String strProp) { this.strProp = strProp; } public ThirdBean getThirdBean() { return thirdBean; } @Autowired(required=true) public void setThirdBean(ThirdBean thirdBean) { this.thirdBean = thirdBean; } public void outPutAll() { System.out.println("output start>>>>"); System.out.printf("intProp:%d,strProp:%s %n",intProp,strProp); firstBean.showMessage(strProp); List<Integer> list=thirdBean.getListProp(); StringBuffer strBuffer=new StringBuffer(); for(Integer i:list) { strBuffer.append(i.toString()+","); } System.out.println(strBuffer.toString()); System.out.println("output end<<<<"); } }
注意@Autowired默認是按byType模式進行自動裝配(即:bean的類型與被標注的成員:字段、屬性、構造函數參數類型相同即為匹配注入),如果存在注冊多個相同的bean類型,這時可能會報錯,因為Spring容器不知道使用哪個類型進行注入實例,如上面示例的Bean配置文件中的FirstBean, 定義了有兩個,只是id不同,那么這種情況我們就需要使用@Qualifier("firstBean2")來顯式指定注入哪個bean;
另外@Autowired(required=true)中的required表示是否必需依賴注入,如果為true,且在bean配置文件中沒有找到注入的bean則會報錯,如果為false則在注入失敗時忽略,即為默認值。
最后我們運行SpringDemoApp,最終結果如下:(大家可能注意到初始回調方法、銷毀回調方法分別調用了2次,都打印了2次,這是因為在bean配置文件中,FirstBean注冊了兩次,雖然名字不同,雖然只用了其中的一個,但這兩個方法是與同一個bean類型有關,同時scope配置為了singleton,由於是單例,故需要為每一個注冊的bean都初始化一下,經過認證,如果把scope改為prototype將只會出現一次)
2.6通過定義Spring Bean配置類+注解來實現Bean的注冊及依賴注入的設置 ,具體實現代碼如下
SpringBeansConfig bean配置類:(用以取代beans.xml配置文件)
package cn.zuowenjun.java; import java.util.Arrays; import org.springframework.context.annotation.*; @Configuration public class SpringBeansConfig { @Bean(initMethod="init",destroyMethod="destroy") @Scope(value="prototype") public FirstBean firstBean() { FirstBean firstBeanObj= new FirstBean(); firstBeanObj.setMessage("i love java!"); return firstBeanObj; } @Bean public SecondBean secondBean() { return new SecondBean(666,"夢在旅途",firstBean()); } @Bean public ThirdBean thirdBean() { ThirdBean thirdBeanObj= new ThirdBean(); thirdBeanObj.setListProp(Arrays.asList(1,2,3,4,5,6,7,8,9)); return thirdBeanObj; } }
然后在main方法添加如下代碼:(這次使用的是AnnotationConfigApplicationContext 的IOC容器,無需bean xml配置文件)
AnnotationConfigApplicationContext annoCfgAppContext=new AnnotationConfigApplicationContext(SpringBeansConfig.class); annoCfgAppContext.start(); FirstBean firstBean= (FirstBean)annoCfgAppContext.getBean("firstBean"); firstBean.showMessage("firstBean單獨"); SecondBean secondBean= annoCfgAppContext.getBean(SecondBean.class); secondBean.outPutAll(); annoCfgAppContext.close(); annoCfgAppContext.registerShutdownHook();
運行效果如下圖示:(發現沒有,destroy方法沒有輸出,知道為什么?因為FirstBean的scope使用了prototype模式,如果不指定或指定為singleton,則能正常輸出destroy方法打印的內容,但具體原因大家自行查找資料)
2.7 定義相關Spring事件處理器類(繼承自ApplicationListener泛型接口,泛型參數為具體的事件ApplicationEvent的子類),從而訂閱相關的Spring事件觸發的事件方法,Spring 提供了以下的標准事件接口:(圖片來源網絡)
示例代碼如下:(訂閱開始及停止事件)
package cn.zuowenjun.java; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextStartedEvent; public class CStartEventHandler implements ApplicationListener<ContextStartedEvent> { @Override public void onApplicationEvent(ContextStartedEvent event) { String appName= event.getApplicationContext().getDisplayName(); System.out.println(appName + " was Started!"); } } package cn.zuowenjun.java; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextStoppedEvent; public class CStopEventHandler implements ApplicationListener<ContextStoppedEvent> { @Override public void onApplicationEvent(ContextStoppedEvent event) { String appName= event.getApplicationContext().getDisplayName(); System.out.println(appName + " was Stopped!"); } } //SpringBeansConfig 類中增加注冊上述兩個Bean(如果是用XML配置文件則換成在XML中定義相關的bean元素) @Bean public CStartEventHandler cStartEventHandler() { return new CStartEventHandler(); } @Bean public CStopEventHandler cStopEventHandler() { return new CStopEventHandler(); } //最后main方法運行如下邏輯: AnnotationConfigApplicationContext annoCfgAppContext=new AnnotationConfigApplicationContext(SpringBeansConfig.class); annoCfgAppContext.setDisplayName("SpringDemoAppContext"); annoCfgAppContext.start(); //start,以觸發start事件 SecondBean secondBean= annoCfgAppContext.getBean(SecondBean.class); secondBean.outPutAll(); annoCfgAppContext.stop();//stop,以觸發stop事件 annoCfgAppContext.close(); annoCfgAppContext.registerShutdownHook();
運行結果如下圖示:
當然除了標准的事件外,我們還可以自定義事件,主要是分別定義:繼承ApplicationEvent (即:自定義事件消息類)、實現ApplicationEventPublisherAware(即:事件發布類或稱觸發事件類)、實現ApplicationListener(即:實現訂閱事件類),由於篇幅有限,具體的實現代碼在此不再貼出,可參見:https://www.w3cschool.cn/wkspring/7jho1ict.html
特別說明:以上通過各種示例代碼分別演示了:通過IOC容器獲得bean的實例對象、基於bean配置文件實現構造函數及屬性依賴注入、基於注解及Bean配置類實現構造函數及屬性依賴注入等,但其實示例代碼中並沒有充分發揮依賴注入的功能或者說是核心思想(解除直接依賴,依賴抽象,而不能依賴具體實現),因為都是基於普通類來實現注入的,只是把實例化的過程(new)交給了Spring容器而矣,依賴仍然存在,無法替換,那要如何做呢?其實很簡單,我們只需要給對應的bean定義接口,然后bean去實現這個接口,我們在再bean配置中注冊實現類即可,這樣就真正的發揮了IOC的作用了,代碼改造如下:
//定義一個IFirstBean 接口 package cn.zuowenjun.java; public interface IFirstBean { String getMessage(); void setMessage(String message); void showMessage(String name); void init(); void destroy(); } //FirstBean 實現IFirstBean 接口 package cn.zuowenjun.java; import javax.annotation.*; public class FirstBean implements IFirstBean { private String uuidStr = java.util.UUID.randomUUID().toString(); private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public void showMessage(String name) { System.out.printf("hello,%1$s,Message:%2$s UUID:%3$s %n", name, message, uuidStr); } public void throwEx() throws Exception { throw new Exception("throw a new Exception!"); } @PostConstruct public void init() { System.out.println(uuidStr + ":init..."); } @PreDestroy public void destroy() { System.out.println("destroy..."); } } package cn.zuowenjun.java; import java.util.List; import org.springframework.beans.factory.annotation.*; public class SecondBean { private int intProp; private String strProp; private ThirdBean thirdBean; private IFirstBean firstBean=null;//這里依賴關系由實現類改為接口IFirstBean @Autowired(required=true) public SecondBean(int ipro,String sPro,@Qualifier("firstBean2") IFirstBean frtBean) { this.intProp=ipro; this.strProp=sPro; this.firstBean=frtBean; } ....省略其它代碼 } //main方法運行如下代碼: AnnotationConfigApplicationContext annoCfgAppContext=new AnnotationConfigApplicationContext(SpringBeansConfig.class); annoCfgAppContext.setDisplayName("SpringDemoAppContext"); annoCfgAppContext.start(); IFirstBean firstBean= (IFirstBean)annoCfgAppContext.getBean(IFirstBean.class); firstBean.showMessage("firstBean單獨"); SecondBean secondBean= annoCfgAppContext.getBean(SecondBean.class); secondBean.outPutAll(); annoCfgAppContext.stop(); annoCfgAppContext.close(); annoCfgAppContext.registerShutdownHook();
最終運行的結果與之前是一樣的,當然這樣歸功於Spring的byType(找同類型或父類或實現類) 、byName(找注冊bean的id)依賴注入方式。
三、 使用Spring的面向切面功能(AOP)
之前我曾寫過一篇專門介紹AOP的文章《C# 實現AOP 的幾種常見方式》,雖然那是用C#實現的,但思路及實現原理是一樣的,今天我們就借助於Spring AOP框架來實現面向切面編程的能力,由於AOP的一些原理及基本實現已經講過了不在重述,這里就直接演示實現過程及效果。
3.1下載並安裝AspectJ框架環境
下載頁面:https://www.eclipse.org/aspectj/downloads.php,找到合適的下載版本鏈接(最新穩定版本)點擊下載,如下圖示:
Latest Stable Release翻譯成中文就是:最新穩定版本,每個版本都有兩個下載鏈接,一個是編譯后的字節碼包,一個是src源碼包,我這里下載的是可執行的字節碼包,下載后需要通過java命令運行該jar包(其實就是一個安裝程序,安裝后會釋放出相關的jar包)
java -jar aspectj-xxxx.jar
項目中引用AspectJ安裝lib目錄下的相關jar包,引用外部JAR包之前都有說明在此不重述,引入后效果:
至此一個Spring的AOP項目開發環境就准備OK了,當然AOP環境OK了,還需要了解必要的AOP知識要點,因為網上有很多相關的介紹,我就不在一 一說明了,可參考:
https://www.cnblogs.com/hongwz/p/5764917.html 、https://blog.csdn.net/csdn_terence/article/details/55804421
以下是我的簡單理解:
aspect:切面,實現各種通知的API方法集合類;
pointcut:切入點,指從哪個位置進行攔截並通知;
joinpoint:連接點,被攔截到的對象信息(一般指:方法,屬性、構造器);
advice:通知,指被攔截后在相應的時機(有:前置、后置、異常、最終、環繞通知)處理的方法
Target object:目標對象,被代理的對象
Weaving:織入,將切面應用到目標對象並導致代理對象創建的過程
Introduction:引用,在運行時期,可動態的添加新方法或屬性到現有的類中。
3.2 通過XML配置AOP,實現步驟如下
A.定義一個Aspect的類(MyAspect),里面主要包含各種通知處理方法:
package cn.zuowenjun.java; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect { public void before(JoinPoint point) { System.out.println("call MyAspect.before!" + point.getSignature().getName()); } public void after(JoinPoint point) { System.out.println("call MyAspect.after!" + point.getSignature().getName()); } public void afterReturning(Object retVal){ System.out.println("call MyAspect.afterReturning! return Value:" + retVal); } public void afterThrowing(Exception ex) { System.out.println("call MyAspect.afterThrowing! Exception:" + ex.getMessage()); } public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("call around start-" + pjp.getSignature().getName()); Object obj=pjp.proceed(); System.out.println("call around end-" + pjp.getSignature().getName()); return obj; } }
改造一下IFirstBean及FirstBean,以便后面可以進行AOP的演示(增加一個throwEx方法),改動后如下:
package cn.zuowenjun.java; public interface IFirstBean { String getMessage(); void setMessage(String message); void showMessage(String name); void init(); void destroy(); void throwEx(); } package cn.zuowenjun.java; import javax.annotation.*; public class FirstBean implements IFirstBean { private String uuidStr = java.util.UUID.randomUUID().toString(); private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public void showMessage(String name) { System.out.printf("hello,%1$s,Message:%2$s UUID:%3$s %n", name, message, uuidStr); } public void throwEx() { System.out.println("need throw a Exception"); throw new IllegalArgumentException("a test Exception msg!"); } @PostConstruct public void init() { System.out.println(uuidStr + ":init..."); } @PreDestroy public void destroy() { System.out.println("destroy..."); } }
B.配置AOP,將Aspect類(MyAspect)注冊到Spring 容器中,完整AOP相關的XML配置如下:(為了演示效果,僅保留FirstBean注冊及AOP的配置)
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <context:annotation-config /> <bean id="firstBean" class="cn.zuowenjun.java.FirstBean" init-method="init" destroy-method="destroy" scope="singleton"> <property name="message" value="i love Spring!"></property> </bean> <bean id="myaspectHandler" class="cn.zuowenjun.java.MyAspect"> </bean> <aop:config> <aop:aspect id="myaspect" ref="myaspectHandler"> <aop:pointcut id="showCut" expression="execution(public * cn.zuowenjun.java.*.show*(..))" /> <aop:pointcut id="exCut" expression="execution(public * cn.zuowenjun.java.*.*(..))" /> <aop:pointcut id="getterCut" expression="execution(public Object+ cn.zuowenjun.java.*.get*())" /> <aop:before method="before" pointcut-ref="showCut"/> <aop:after method="after" pointcut-ref="showCut"/> <aop:around method="around" pointcut-ref="getterCut"/> <aop:after-returning method="afterReturning" pointcut-ref="getterCut" returning="retVal"/> <aop:after-throwing method="afterThrowing" pointcut-ref="exCut" throwing="ex"/> </aop:aspect> </aop:config> </beans>
可以看到,配置文件根節點中增加了aop的命名空間,同時增加了AOP的配置節點aop:config,aop:config中分別配置了:aspect(同時ref指向注冊的bean的id)、pointcut、以及相關的通知方法(都關聯到pointcut),其中關於pointcut的表達式的用法比較復雜,深入了解可以參見:https://blog.csdn.net/zhengchao1991/article/details/53391244
最后在main方法中運行如下代碼:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); context.start(); IFirstBean firstBean = (IFirstBean)context.getBean("firstBean"); firstBean.showMessage("夢在旅途"); String msg= firstBean.getMessage(); System.out.println("print getMessage:" + msg); try { firstBean.throwEx(); }catch(Exception ex) { System.out.println("catch Exception:" + ex.getMessage()); } context.stop(); context.close(); context.registerShutdownHook();
運行效果如下:
3.3通過注解配置AOP,我們只需改造之前的MyAspect類,然后加上相關的AOP注解即可實現無需XML配置,完善后的MyAspect類代碼如下:
package cn.zuowenjun.java; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { @Pointcut(value="execution(public * cn.zuowenjun.java.*.show*(..))") private void showCut() { } @Pointcut(value="execution(public * cn.zuowenjun.java.*.*(..))") private void exCut() { } @Pointcut(value="execution(public Object+ cn.zuowenjun.java.*.get*())") private void getterCut() { } @Before("showCut()") public void before(JoinPoint point) { System.out.println("call MyAspect.before!" + point.getSignature().getName()); } @After("showCut()") public void after(JoinPoint point) { System.out.println("call MyAspect.after!" + point.getSignature().getName()); } @AfterReturning(pointcut="getterCut()",returning="retVal") public void afterReturning(Object retVal){ System.out.println("call MyAspect.afterReturning! return Value:" + retVal); } @AfterThrowing(pointcut="exCut()",throwing="ex") public void afterThrowing(Exception ex) { System.out.println("call MyAspect.afterThrowing! Exception:" + ex.getMessage()); } @Around("getterCut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("call around start-" + pjp.getSignature().getName()); Object obj=pjp.proceed(); System.out.println("call around end-" + pjp.getSignature().getName()); return obj; } }
從上面代碼可以看出差異的部份,僅僅在類上增加了:@Aspect、@Component ,在對應的方法上增加了@Pointcut、@Before、@After、@AfterReturning、@AfterThrowing、@Around,每個被特定注解標記的方法都代表着相應的作用,從注解含義就可以看得出來。
當然還有一個很重要的點的就是XML配置中還需要增加一點配置,如下:
<aop:aspectj-autoproxy /> <bean id="myaspect" class="cn.zuowenjun.java.MyAspect"> </bean>
最后直接運行之前的main方法(邏輯全部都不用變),得到的輸出結果與上面3.2節(用XML配置AOP)顯示的一樣。
話說雖然省掉了AOP的XML配置但仍然還有一點配置,能不能完全不配置呢?答案是可以的,我們在上面2.6節介紹過可以使用定義Spring Bean配置類+注解來實現Bean的注冊及依賴注入的設置,那這里同樣我們將MyAspect加入到Spring Bean配置類中,並在Spring Bean配置類(示例類:SpringBeansConfig)上添加@EnableAspectJAutoProxy注解即可,如下所示:
package cn.zuowenjun.java; import java.util.Arrays; import org.springframework.context.annotation.*; @Configuration @EnableAspectJAutoProxy public class SpringBeansConfig { @Bean(initMethod="init",destroyMethod="destroy") @Scope(value="prototype") public FirstBean firstBean() { FirstBean firstBeanObj= new FirstBean(); firstBeanObj.setMessage("i love java!"); return firstBeanObj; } @Bean public MyAspect myAspect() { return new MyAspect(); } } //main方法代碼: AnnotationConfigApplicationContext annoCfgAppContext=new AnnotationConfigApplicationContext(SpringBeansConfig.class); annoCfgAppContext.setDisplayName("SpringDemoAppContext"); annoCfgAppContext.start(); IFirstBean firstBean = (IFirstBean)annoCfgAppContext.getBean(IFirstBean.class); firstBean.showMessage("夢在旅途"); String msg= firstBean.getMessage(); System.out.println("print getMessage:" + msg); try { firstBean.throwEx(); }catch(Exception ex) { System.out.println("catch Exception:" + ex.getMessage()); } annoCfgAppContext.stop(); annoCfgAppContext.close(); annoCfgAppContext.registerShutdownHook();
最終運行的結果仍然與3.2節(XML配置AOP)、注解+XML配置AOP 相同,說明完全可以不用XML了。
好了,本文的主題(IOC、AOP)就分享到這里,我們總結一下吧!
最后小結:
1.Spring的IOC主要解決抽象與實現分離,代碼中若有依賴盡可能的使用抽象類或接口,比如示例中的:(SecondBean依賴IFristBean),實現部份可以單獨一個項目(JAR包),然后通過 XML配置或注解配置注入即可。我們在配置中一般配置實現類。
2.Spring的AOP(准確的說是:AspectJ AOP)主要解決橫切面問題,消除功能相同的代碼邏輯,比如:日志、監控、異常捕獲、驗證、攔截、過濾等,可以把相同的業務邏輯代碼集中到某一類Aspec類中,便於統一管理與擴展。同樣支持XML配置或注解配置
3.雖然IOC、AOP功能強大,但配置有些繁瑣,依賴的JAR包要自己手動添加不夠智能,需要能有像VS的NUGET包一樣的依賴管理工具,JAVA這邊MAVEN就出場了,解決依賴問題,后續將會分享基於MAVEN來快速構建Spring項目。
PS:本篇文章涉及的知識點雖然只有IOC、AOP但里面包括的細節非常多,我也是在工作之余反復學習與嘗試,希望對JAVA新手或對Spring不了解的人有幫助,謝謝!若覺得幫助到你,支持一下吧。^ v ^
補充一點小知識:
1.在Eclipse調試或查看JDK源代碼方法:
方法一:選擇項目右鍵->build path->Configure Buid Path->切換到Libraries頁簽->展開JRE System Libararys->找到rt.jar,並展開->選中soure attachment->點擊右側的edit按鈕,然后選擇外部路徑(external location)->瀏覽文件(external file)->選擇JRE的安裝目錄下的src,zip(這里面包含源碼),最后apply應用即可,如下圖示:
方法二:直接參見這篇博文:http://www.cnblogs.com/Keith-Fong/p/9570375.html
2.在Eclipse調試或查看Spring源代碼方法:
當我們通過F3查看定義時,如果只是顯示元數據定義而無法顯示源代碼,則可以點擊“Attach Source”,然后選擇對應的組件的源碼包,如:spring-context-5.1.2.RELEASE-sources.jar ,Spring各源碼包均以:sources.jar結尾
3.除了自己手動從Spring官網下載相關的JAR包並引入外,還可以通過在Eclipse上安裝相關的Spring插件(Spring Tool Suite),安裝后就可以直接在創建的時候選擇Spring的模板項目即可,相關介紹文章可參考:
https://blog.csdn.net/qq_26584263/article/details/71513924