1. 程式人生 > >JAVA WEB快速入門之通過一個簡單的Spring專案瞭解Spring的核心(AOP、IOC)

JAVA WEB快速入門之通過一個簡單的Spring專案瞭解Spring的核心(AOP、IOC)

接上篇《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(常用實現類:FileSystemXmlApplicationContextClassPathXmlApplicationContext、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 ^