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)搭建的,好了廢話很少說,直接進入主題。php

什麼是Spring?html

Spring是一個開放源代碼的設計層面框架,他解決的是業務邏輯層和其餘各層的鬆耦合問題,所以它將面向接口的編程思想貫穿整個系統應用...詳見百度百科:https://baike.baidu.com/item/spring/85061java

核心模塊以下圖示:(來源網絡)程序員

依賴關係:(來源網絡)spring

1、創建一個Spring項目:express

1.1打開eclipse,依次操做:File->New->Java Project,而後設置一些必要的項目屬性(相似操做在上一篇),最後finish建立完成一個空的JAVA Project,注意目前並無Spring環境。以下圖示:apache

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)app

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框架。

2、 使用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知識要點,由於網上有不少相關的介紹,我就不在一 一說明了,可參考:

http://www.javashuo.com/article/p-rdagsfro-db.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的模板項目便可,相關介紹文章可參考:

http://www.javashuo.com/article/p-rigpqmku-hw.html

相關文章
相關標籤/搜索