Spring入門

說明:本文主要內容來自慕課網。配合視頻食用口味更佳。
主要是順著已經學習的視頻順序總結一遍,以深化理解和方便日后復習。
本部分只包含IOC和AOP。

第一章 概述

Spring 入門課程簡介

本門課程包含的內容:
Spring簡介
IOC(配置、注解)
Bean(配置、注解)
AOP(配置、注解、AspetJ、API)

Spring概況

Spring是什么

  • Spring是一個開源框架,最初為了解決企業應用開發的復雜性而創建的,但現在已經不止應用于企業應用
  • 是一個輕量級的控制反轉(IOC)和面向切面(AOP)的容器框架
    • 大小與開銷兩方面而言Spring都是輕量的
    • 通過控制反轉(IOC)的技術達到松耦合的目的
    • 提供了面向切面編程(AOP)的豐富支持,允許通過分離應用的業務邏輯與系統級服務進行內聚性的開發
    • 包含并管理應用對象的配置和生命周期,這個意義上是一種容器。
    • 將簡單的組件配置、組合成為復雜的應用,這個意義上是框架

為什么是Spring

  • 在Spring上開發應用簡單
  • 在Spring上開發應用方便
  • 在Spring上開發應用快捷

Spring作用

  • 容器
  • 提供了多種技術的支持
    --JMS
    --MQ支持
    --UnitTest
  • AOP(事務管理、日志等)
  • 提供了眾多方便應用的輔助類(JDBC Template等)
  • 對主流應用框架(Hibernate等)提供了良好的支持

適用范圍

  • 構建企業應用(SpringMVC+Spring+Hibernate/MyBatis)
  • 單獨使用Bean容器(Bean管理)
  • 單獨使用AOP進行切面處理
  • 其他的Spring功能:如:對消息的支持等
  • 在互聯網中的應用

Spring框架

Spring框架總覽

框架

什么是框架

維基百科:軟件框架,通常是指為了實現某個業界標準或者完成特定基本任務的軟件組件規范,也指為了實現某個軟件組織規范時,提供規范所要求之基礎功能的軟件產品。

通俗的說,框架就是制定一套規范或者規則(思想),大家(程序員)在該規范或者規程(思想)下工作。用現實生活中的比喻就是:使用別人搭好的舞臺,你來做表演。

框架的特點

  • 半成品
  • 封裝了特定的處理流程和控制邏輯
  • 成熟的、不斷升級的軟件

框架與類庫的區別

  • 框架一般是封裝了邏輯、高內聚的,類庫則是松散的工具組合。
  • 框架專注于某一領域,類庫則是更通用的。
框架于類庫區別
  • 類庫通過不同的方式組裝成不同的框架

為什么使用框架

  • 軟件系統日趨復雜
  • 重用度高,開發效率和質量提高
  • 軟件設計人員要專注于對領域的了解,使需求分析更充分
  • 易于上手、快速解決問題

第二章 Spring IOC 容器

接口與面向接口編程

接口

  • 用于溝通的中介物的抽象化
  • 實體把自己提供給外界的一種抽象化說明,用以由內部操作分離出外部溝通方式,使其能被修改內部而不影響外界其他實體與其交互的方式。
  • 對應Java接口即聲明,聲明了哪些方法對外提供的。
  • 在Java8中,接口可以擁有方法體

面向接口編程

  • 結構設計中,分清層次及調用關系,每層只向外(上層)提供一組功能接口,各層間僅依賴接口而非實現類
  • 接口實現的變動不影響各層間的調用,這一點在公共服務中尤為重要
  • “面向接口編程”中的“接口”是用于隱藏具體實現和實現多態性的組件

什么是IOC

站在過程的角度看

IOC:控制反轉,控制權的轉移,不再是應用程序本身-->依賴對象的創建和維護,而是由外部容器-->依賴對象的創建和維護

站在主體的角度看

DI(依賴注入)是其中一種實現方式
2004年,Martin Fowler探討了同一個問題,既然IoC是控制反轉,那么 到底是“哪些方面被控制反轉了呢?”,經過詳細的分析和論證后,他得出了答案:“獲得依賴對象的過程被反轉了”。控制被反轉之后,獲得依賴對象的過程由自身管理變成了由IOC容器的主動注入。于是,他給“控制反轉” 取了一個更合適的名字“依賴注入(Dependency Injection)”。他的這個答案,實際上給出了實現IoC的方法:注入。
所謂依賴注入,就是由IoC容器 在運行期間,動態地將某種依賴關系注入到對象之中。

目的

創建對象并且組裝對象之間的關系

IOC簡單類比

通過中介找房子 通過IOC使用對象
找中介 找IOC容器
中介介紹房子 容器返回對象
租房、入住 使用對象

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.xsd" >
     
    //這是我們要配置的內容
    <bean id="oneInterface" class="com.imooc.ioc.interfaces.OneInterfaceImpl"></bean>

 </beans>

Java bean是什么

Bean容器初始化

基礎

兩個包:

  • org.springframework.beans
  • org.springframework.context

容器:

  • BeanFactory提供配置結構和基本功能,加載并初始化Bean
  • ApplicationContext保存了Bean對象,并在Spring中廣泛使用

BeanFactory和ApplicationContext是Spring兩種很重要的容器,前者提供了最基本的依賴注入的支持,而后者在繼承前者的基礎進行了功能的拓展,例如增加了事件傳播,資源訪問和國際化的消息訪問等功能。

方式---ApplicationContext

1. 本地文件

FileSystemXmlApplicationContext

FileSystemXmlApplicationContext context = new 
FileSystemXmlApplicationContext("F:/workspace/appcontext.xml");

2. 類路徑

ClassPathXmlApplicationContext

ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("classpath:spring-context.xml");

3. Web應用

依賴servletListener

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderServlet</listener-class>
</listener>
<servlet>
    <servlet-name>context</servlet-name>
    <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
    <load-on-startup>on</load-on-startup>
</servlet>

Spring注入方式

Spring注入是指在啟動Spring容器加載bean配置的時候,完成對變量的賦值行為。常用的兩種注入方式設值注入構造注入

設值注入---property

通過setter注入。

<?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.xsd" >

      <bean id="injectionService" class="com.imooc.ioc.injection.service.InjectionServiceImpl">
            <property name="injectionDAO" ref="injectionDAO"></property>
      </bean>


      <bean id="injectionDAO" class="com.imooc.ioc.injection.dao.InjectionDAOImpl"></bean>

 </beans>

構造注入---constructor-arg

通過構造器注入。

<?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.xsd" >


        <bean id="injectionService" class="com.imooc.ioc.injection.service.InjectionServiceImpl">
            <constructor-arg name="injectionDAO" ref="injectionDAO"></constructor-arg>
        </bean>

        <bean id="injectionDAO" class="com.imooc.ioc.injection.dao.InjectionDAOImpl"></bean>

 </beans>

第三章:Spring Bean裝配(上)

Bean的配置項及作用域

Bean配置項

屬性 描述
id|name 這個屬性指定唯一的 bean 標識符。在基于 XML 的配置元數據中,可以使用 id 或 name 屬性來指定 bean 標識符
class 這個屬性是強制性的,并且指定用來創建 bean 的bean 類
scope 這個屬性指定由特定的 bean 定義創建的對象的作用域
constructor-arg 用來注入依賴關系
properties 用來注入依賴關系
autowiring mode 用來注入依賴關系
lazy-initialization mode 延遲初始化的 bean 告訴 IoC 容器在它第一次被請求時,而不是在啟動時去創建一個 bean 實例
initialization() 在 bean 的所有必需的屬性被容器設置之后,調用回調方法
destruction() 當包含該 bean 的容器被銷毀時,使用回調方法

Bean的作用域

添加方式

<bean id=" " class=" " scope="singleton"></bean>
作用域 描述
singleton 單例,指一個Bean容器中只存在一份
prototype 每次請求(每次使用)創建新的實例,destroy方式不生效
request 每次http請求創建一個實例且僅在當前request內有效
session 同上,每次http請求創建,當前session內有效
global session 基于portlet的web中有效(portlet定義了global session),如果在web中,同session

Bean的生命周期

Bean的生命周期
  1. 首先容器啟動后,會對scope為singleton且非懶加載的bean進行實例化
  2. 按照Bean定義信息配置信息,注入所有的屬性
  3. 如果Bean實現了BeanNameAware接口,會回調該接口的setBeanName()方法,傳入該Bean的id,此時該Bean就獲得了自己在配置文件中的id
  4. 如果Bean實現了BeanFactoryAware接口,會回調該接口的setBeanFactory()方法,傳入該Bean的BeanFactory,這樣該Bean就獲得了自己所在的BeanFactory
  5. 如果Bean實現了ApplicationContextAware接口,會回調該接口的setApplicationContext()方法,傳入該Bean的ApplicationContext,這樣該Bean就獲得了自己所在的ApplicationContext,
  6. 如果有Bean實現了BeanPostProcessor接口,則會回調該接口的postProcessBeforeInitialzation()方法
  7. 如果Bean實現了InitializingBean接口,則會回調該接口的afterPropertiesSet()方法
  8. 如果Bean配置了init-method方法,則會執行init-method配置的方法
  9. 如果有Bean實現了BeanPostProcessor接口,則會回調該接口的postProcessAfterInitialization()方法
    10.經過流程9之后,就可以正式使用該Bean了,對于scope為singleton的Bean,Spring的ioc容器中會緩存一份該bean的實例,而對于scope為prototype的Bean,每次被調用都會new一個新的對象,期生命周期就交給調用方管理了,不再是Spring容器進行管理了
    11.容器關閉后,如果Bean實現了DisposableBean接口,則會回調該接口的destroy()方法
    12.如果Bean配置了destroy-method方法,則會執行destroy-method配置的方法

至此,整個Bean的生命周期結束

創建與銷毀

方法1:實現InitializingBean和DisposableBean接口

這兩個接口都只包含一個方法。

  • 實現InitializingBean接口的afterPropertiesSet()方法可以在Bean屬性值設置好之后做一些操作
  • 實現DisposableBean接口的destroy()方法可以在銷毀Bean之前做一些操作。

如下:

public class GiraffeService implements InitializingBean,DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("執行InitializingBean接口的afterPropertiesSet方法");
    }
 
    @Override
    public void destroy() throws Exception {
        System.out.println("執行DisposableBean接口的destroy方法");
    }
}

這種方法比較簡單,但是不建議使用。因為這樣會將Bean的實現和Spring框架耦合在一起。

方法2:在bean的配置文件中指定init-method和destroy-method方法

Spring允許我們創建自己的init方法和destroy方法,只要在Bean的配置文件中指定init-methoddestroy-method的值就可以在Bean初始化時和銷毀之前執行一些操作。

配置文件中的配置:

<bean name="giraffeService" class="com.giraffe.spring.service.GiraffeService" 
                            init-method="initMethod" destroy-method="destroyMethod">
</bean>

代碼:

public class GiraffeService {
    //通過<bean>的destroy-method屬性指定的銷毀方法
    public void destroyMethod() throws Exception {
        System.out.println("執行配置的destroy-method");
    }
 
    //通過<bean>的init-method屬性指定的初始化方法
    public void initMethod() throws Exception {
        System.out.println("執行配置的init-method");
    }
}

需要注意的是自定義的init-method和post-method方法可以拋異常但是不能有參數。
這種方式比較推薦,因為可以自己創建方法,無需將Bean的實現直接依賴于spring的框架

方法3:使用@PostConstruct和@PreDestroy注解

  • 除了xml配置的方式,Spring也支持用@PostConstruct@PreDestroy注解來指定init和destroy方法。
  • 這兩個注解均在javax.annotation包中。
  • 為了注解可以生效,需要在配置文件中定義org.springframework.context.annotation.CommonAnnotationBeanPostProcessorcontext:annotation-config

配置文件:

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />

代碼:

public class GiraffeService {
    @PostConstruct
    public void initPostConstruct(){
        System.out.println("執行PostConstruct注解標注的方法");
    }
 
    @PreDestroy
    public void preDestroy(){
        System.out.println("執行preDestroy注解標注的方法");
    }
 
}

方法4:配置全局默認初始化、銷毀方法

default-init-method=" "
default-destroy-method=" "

<?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.xsd" 
        default-init-method="defautInit" default-destroy-method="defaultDestroy">

 </beans>

Bean的生命周期示例

  • 定義一個Person
  • 實現了BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean五個接口
  • 在applicationContext.xml文件中配置了該Bean的id為person1,并且配置了init-methoddestroy-method,為該Bean配置了屬性name為jack的值
  • 然后定義了一個MyBeanPostProcessor方法,該方法實現了BeanPostProcessor接口,且在applicationContext.xml文件中配置了該方法的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:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.2.xsd">
                    
     <bean id="person1" destroy-method="myDestroy" 
            init-method="myInit" class="com.test.spring.life.Person">
        <property name="name">
            <value>jack</value>
        </property>
    </bean>
    
    <!-- 配置自定義的后置處理器 -->
     <bean id="postProcessor" class="com.pingan.spring.life.MyBeanPostProcessor" />
</beans>
public class Person implements BeanNameAware, BeanFactoryAware,
        ApplicationContextAware, InitializingBean, DisposableBean {

    private String name;   
    public Person() {
        System.out.println("PersonService類構造方法");
    }
    
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        System.out.println("set方法被調用");
    }

    //自定義的初始化函數
    public void myInit() {
        System.out.println("myInit被調用");
    }
    
    //自定義的銷毀方法
    public void myDestroy() {
        System.out.println("myDestroy被調用");
    }

    public void destroy() throws Exception {
        // TODO Auto-generated method stub
     System.out.println("destory被調用");
    }

    public void afterPropertiesSet() throws Exception {
        // TODO Auto-generated method stub
        System.out.println("afterPropertiesSet被調用");
    }

    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        // TODO Auto-generated method stub
       System.out.println("setApplicationContext被調用");
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        // TODO Auto-generated method stub
         System.out.println("setBeanFactory被調用,beanFactory");
    }

    public void setBeanName(String beanName) {
        // TODO Auto-generated method stub
        System.out.println("setBeanName被調用,beanName:" + beanName);
    }
    
    public String toString() {
        return "name is :" + name;
    }
public class MyBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean,
            String beanName) throws BeansException {
        // TODO Auto-generated method stub
        
        System.out.println("postProcessBeforeInitialization被調用");
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("postProcessAfterInitialization被調用");
        return bean;
    }

}
public class AcPersonServiceTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        System.out.println("開始初始化容器");
        ApplicationContext ac = new ClassPathXmlApplicationContext("com/test/spring/life/applicationContext.xml");
        
        System.out.println("xml加載完畢");
        Person person1 = (Person) ac.getBean("person1");
        System.out.println(person1);        
        System.out.println("關閉容器");
        ((ClassPathXmlApplicationContext)ac).close();
        
    }
}

我們啟動容器,可以看到整個調用過程:

開始初始化容器
九月 25, 2016 10:44:50 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@b4aa453: startup date [Sun Sep 25 22:44:50 CST 2016]; root of context hierarchy
九月 25, 2016 10:44:50 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [com/test/spring/life/applicationContext.xml]
Person類構造方法
set方法被調用
setBeanName被調用,beanName:person1
setBeanFactory被調用,beanFactory
setApplicationContext被調用
postProcessBeforeInitialization被調用
afterPropertiesSet被調用
myInit被調用
postProcessAfterInitialization被調用
xml加載完畢
name is :jack
關閉容器
九月 25, 2016 10:44:51 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@b4aa453: startup date [Sun Sep 25 22:44:50 CST 2016]; root of context hierarchy
destory被調用
myDestroy被調用

Aware接口

有些時候我們需要在Bean的初始化中使用Spring框架自身的一些對象來執行一些操作,比如獲取ServletContext的一些參數,獲取ApplicaitionContext中的BeanDefinition的名字,獲取Bean在容器中的名字等等。為了讓Bean可以獲取到框架自身的一些對象,Spring提供了一組名為___Aware接口

這些接口均繼承于org.springframework.beans.factory.Aware標記接口,并提供一個將由Bean實現的set___()方法,Spring通過基于setter的依賴注入方式使相應的對象可以被Bean使用。

一些重要的Aware接口

接口 作用
ApplicationContextAware 獲得ApplicationContext對象,可以用來獲取所有Bean definition的名字
BeanFactoryAware 獲得BeanFactory對象,可以用來檢測Bean的作用域
BeanNameAware 獲得Bean在配置文件中定義的名字
ResourceLoaderAware 獲得ResourceLoader對象,可以獲得classpath中某個文件
ServletContextAware 在一個MVC應用中可以獲取ServletContext對象,可以讀取context中的參數
ServletConfigAware 在一個MVC應用中可以獲取ServletConfig對象,可以讀取config中的參數

自動裝配(Autowiring)

具體參考
博客
為什么Spring要支持Autowire(自動裝配)

什么是裝配

在spring中,對象無需自己查找或創建與其關聯的其他對象,容器負責把需要相互協作的對象引用賦予各個對象。
創建對象之間協作關系的行為通常稱為裝配。

Spring 裝配的方式

  1. 隱式Bean的發現機制和自動裝配
    所謂自動裝配,就是將一個Bean注入到其他Bean的Property中,類似于以下:
<bean id="customer" class="com.lei.common.Customer" autowire="byName" />
  1. 顯式裝配:
    • 在Java中顯式裝配
    • 在XML中顯式裝配

為什么自動裝配

Spring引入Autowire(自動裝配)機制就是為了解決<bean>標簽下<property>標簽過多的問題

5種自動裝配模式

模式 描述
no 默認情況下,不自動裝配,通過“ref”attribute手動設定
byName 由屬性名自動裝配。根據Property的Name自動裝配,如果一個bean的name,和另一個bean中的Property的name相同,則自動裝配這個bean到Property中
byType 由屬性數據類型自動裝配。根據Property的數據類型(Type)自動裝配,如果一個bean的數據類型,兼容另一個bean中Property的數據類型,則自動裝配
constructor 根據構造函數參數的數據類型,進行byType模式的自動裝配
autodetect 如果發現默認的構造函數,用constructor模式,否則,用byType模式

Resource

簡介

在Spring內部實現機制,針對于資源文件(配置的xml文件)有一個統一的接口Resource

常用方法

方法 說明
exists() 用于判斷對應的資源是否存在
isReadable() 用于判斷對應資源的內容是否可讀。需要注意的是當其結果為true的時候,其內容未必真的可讀,但如果返回false,則其內容必定不可讀。
isOpen() 用于判斷當前資源是否代表一個已打開的輸入流,如果結果為true,則表示當前資源的輸入流不可多次讀取,而且在讀取以后需要對它進行關閉,以防止內存泄露。該方法主要針對于InputStreamResource,實現類中只有它的返回結果為true,其他都為falses
getURL() 返回當前資源對應的URL。如果當前資源不能解析為一個URL則會拋出異常。如ByteArrayResource就不能解析為一個URL
getFile() 返回當前資源對應的File。如果當前資源不能以絕對路徑解析為一個File則會拋出異常。如ByteArrayResource就不能解析為一個File。
getInputStream() 獲取當前資源代表的輸入流。除了InputStreamResource以外,其它Resource實現類每次調用getInputStream()方法都將返回一個全新的InputStream。

常用實現類

實現類 說明
ClassPathResource 獲取類路徑下的資源文件。假設有一個資源文件test.txt在類路徑下,new ClassPathResource("test.txt")
FileSystemResource 獲取文件系統里面的資源。
UrlResource 可用來代表URL對應的資源,它對URL做了一個簡單的封裝。通過給定一個URL地址,我們就能構建一個UrlResource。
ByteArrayResource 針對于字節數組封裝的資源,它的構建需要一個字節數組。
ServletContextResource ServletContextResource持有一個ServletContext的引用,其底層是通過ServletContext的getResource()方法和getResourceAsStream()方法來獲取資源的
InputStreamResource 針對于輸入流封裝的資源,它的構建需要一個輸入流

第四章 注解

Spring的一個核心功能是IOC,就是將Bean初始化加載到容器中,Bean是如何加載到容器的,可以使用Spring注解方式或者Spring XML配置方式。
Spring注解方式減少了配置文件內容,更加便于管理,并且使用注解可以大大提高了開發效率。
下面安裝分類講解Spring中常用的一些注解。

將普通類加入容器形成Bean

spring使用配置文件或者注解的方式進行標識需要處理的java類,從而知道哪些Java類當bean類處理

注解 標注
@Component 標準一個普通的spring Bean類
@Repository 標注一個DAO組件類
@Service 標注一個業務邏輯組件類
@Controller 標注一個控制器組件類

其中@Component、@Repository、@Service、@Controller實質上屬于同一類注解,用法相同,功能相同,區別在于標識組件的類型。

從容器中取Bean(裝配bean)

注解 標注
@Autowired 屬于Spring 的org.springframework.beans.factory.annotation包下,可用于為類的屬性、構造器、方法進行注值
@Resource 不屬于spring的注解,而是來自于JSR-250位于java.annotation包下,使用該annotation為目標bean指定協作者Bean。
@PostConstruct 和 @PreDestroy 方法 實現初始化和銷毀bean之前進行的操作

Spring MVC模塊注解

@Controller---與前端交互

表明該類會作為與前端作交互的控制層組件

  • 通過服務接口定義的提供訪問應用程序的一種行為,解釋用戶的輸入,將其轉換成一個模型然后將試圖呈獻給用戶
@Controller
public class HappyController {
    //do something
}

@RequestMapping---url --> 類或方法

  • 這個注解用于將url映射整個處理類或者特定的處理請求的方法
  • 可以只用通配符!
  • @RequestMapping中可以使用 method =屬性標記其所接受的方法類型,如果不指定方法類型的話,可以使用 HTTP GET/POST 方法請求數據,但是一旦指定方法類型,就只能使用該類型獲取數據
@Controller
@RequestMapping("/happy")
public class HappyController  {

  @Autowired
  private HappyService happyService;

  @RequestMapping(/hello/ *)
  public void sayHello(){
        //請求為 /happy/hello/ * 都會進入這個方法!
        //例如:/happy/hello/123   /happy/hello/adb
        //可以通過get/post 請求
  }

  @RequestMapping(value="/haha",method=RequestMethod.GET)
  public void sayHaHa(){
  //只能通過get請求
  }

}

第五章:Spring AOP

具體參考博客

AOP基本概念及特點

什么是AOP

AOP(Aspect Oriented Programming)意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。

主要功能是:日志記錄,性能統計,安全控制,事務處理,異常處理等

AOP實現方式

  • 預編譯
    AspectJ
  • 運行期間動態代理(JDK動態代理、CGLib動態代理)
    SpringAOP、JbossAOP

AOP幾個關鍵概念

名稱 說明 舉例
切面(Aspect) 散落在系統各處的通用的非業務代碼 日志模塊,權限模塊,事務模塊
連接點(Joinpoint) 被攔截到的點 Spring只支持方法類型的連接點,所以在Spring中連接點指的就是被攔截到的方法
通知(Advice) 攔截到連接點之后要執行的代碼 分為前置、后置、異常、最終、環繞通知五類
切入點(Pointcut) 攔截的方法,連接點攔截后變成切入點 帶有通知的連接點
織入(weave) 通過切入點切入,將切面應用到目標對象并導致代理對象創建的過程 ?
目標對象(Target Object) 代理的目標對象,指要織入的對象模塊 業務代碼
AOP代理(AOP Proxy) AOP框架創建的對象(包括通知方法執行等功能) Spring中的AOP代理可以使JDK動態代理,也可以是CGLIB代理,前者基于接口,后者基于子類

各關鍵概念具體介紹

Advice

說明:

類型 說明
前置通知(Before advcie) 在某連接點(join point)之前執行的通知,但不能阻止連接點前的執行(除非它拋出一個異常)
返回后通知(After returning advice) 在某連接點(join point)正常執行完成后執行的通知
拋出異常后通知(After throwing advice) 在方法拋出異常退出時執行的通知
后通知(After(finally)advice) 當某個連接點退出的時候執行的通知(不論是正常返回還是異常退出)
環繞通知(Around Advice) 包圍一個連接點(join point)的通知

使用方式:

名稱 配置中使用 注解中使用
前置通知(Before advcie) <aop:aspect>里面使用<aop:before> @Before
返回后通知(After returning advice) <aop:aspect>里面使用<aop:after-returning> @AfterReturning
拋出異常后通知(After throwing advice) <aop:aspect>里面使用<aop:after-throwing> @AfterThrowing
后通知(After(finally)advice) aop:aspect>里面使用<aop:after>元素 @After
環繞通知(Around Advice) <aop:aspect>里面使用<aop:around> @Around

通知執行順序:

前置通知→環繞通知連接點之前→連接點執行  1→  環繞通知連接點之后→返回通知→后通知
                                     2→  (如果發生異常)異常通知→后通知

pointcut

切入點表達式 說明
execution 用于匹配方法執行的連接點
within 用于匹配指定類型內的方法執行
this 用于匹配當前AOP代理對象類型的執行方法;this中使用的表達式必須是完整類名,不支持通配符
target 用于匹配當前目標對象類型的執行方法;注意是目標對象的類型匹配,這樣就不包括引入接口也類型匹配;注意target中使用的表達式必須是完整類名,不支持通配符;
args 用于匹配當前執行的方法傳入的參數為指定類型的執行方法;參數類型列表中的參數必須是完整類名,通配符不支持;args屬于動態切入點,這種切入點開銷非常大,非特殊情況最好不要使用;
@within 用于匹配所有持有指定注解類型內的方法;注解類型也必須是完整類名;
@target 用于匹配當前目標對象類型的執行方法,其中目標對象持有指定的注解;注解類型也必須是完整類名;
@args 用于匹配當前執行的方法傳入的參數持有指定注解的執行;注解類型也必須是完整類名;
@annotation 用于匹配當前執行方法持有指定注解的方法;注解類型也必須是完整類名;
bean Spring AOP擴展的,AspectJ沒有對于指示符,用于匹配特定名稱的Bean對象的執行方法;
reference pointcut 表示引用其他命名切入點,只有注解風格支持,XML風格不支持。

定義

  • 一個切入點通過一個普通的方法定義來提供,使用@Pointcut注解

  • 方法返回類型必須為void

  • eg.
    定義一個名為‘anyOldTransfer’,這個切點將匹配任何名為”transfer“的方法執行@Pointcut("execution(* transfer(..))")//the pointcut expression

@Pointcut("execution(* transfer(..))")//the pointcut expression
private void anyOldTransfer(){}//the pointcut signature

組合pointcut

  • 切入點表達式可以通過&&、||、!進行組合,也可以通過名字引入切入點表達式
  • 通過組合,可以建立更加復雜的切入點表達式
@Pointcut("execution(public * (..))")
private void anyPublicOperation(){}

@Pointcut("within(com.xyz.someapp.trading...)")
private void inTrading(){}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation(){}

匹配語法

符號 匹配
* 匹配任何數量字符
.. 匹配任何數量字符的重復,如在類型模式中匹配任何數量子包;而在方法參數模式中匹配任何數量參數。
+ 匹配指定類型的子類型;僅能作為后綴放在類型模式后邊。
例子 匹配 不匹配
java.lang.String 匹配String類型 ?
java.*.String 匹配java包下的任何“一級子包”下的String類型;如匹配java.lang.String 但不匹配java.lang.ss.String
java..* 匹配java包及任何子包下的任何類型;如匹配java.lang.String、java.lang.annotation.Annotation ?
java.lang.*ing 匹配任何java.lang包下的以ing結尾的類型 ?
java.lang.Number+ 匹配java.lang包下的任何Number的自類型;如匹配java.lang.Integer,也匹配java.math.BigInteger ?

實現手法

環境

Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關系也由IOC容器負責管理,要在Spring 中使用AOP,還需要加入這兩個jar包
1、aopalliance.jar
2、aspectjweaver.jar

Spring中 AOP中的兩種代理

  1. Java動態代理
    默認使用 。可以為任何接口實例創建代理了
  2. CGLIB
    當需要代理的類不是代理接口的時候,Spring會切換為使用CGLIB代理,也可強制使用CGLIB

Spring AOP的使用步驟

  1. 定義具體業務邏輯模塊(目標對象)
  2. 定義切面(即實現通知邏輯)
  3. 實現切面邏輯

兩種方式的例子

兩種方式,基于Schema或@AspectJ。

一. 基于Schema的Spring AOP

第一步、定義具體業務模塊(目標對象)

兩個業務模塊都是基于接口

TestAOPDaoImpl .java

public class TestAOPDaoImpl implements TestAOPDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }
}

TestAOPServiceImpl.java

public class TestAOPServiceImpl implements TestAOPService{

    @Autowired
    private TestAOPDao testAOPDao;

    @Override
    public void addUser() {
        testAOPDao.addUser();
    }
}

第二步和第三步、 定義切面(即實現通知邏輯)

aop創建代理后會返回一個 連接點JointPoint,然后在通知中可以通過該連接點實現我們的切面邏輯

日志切面

public class LogAdivice{

    public void myBeforeAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——前置通知——" + methodname);
    }

    public void myAfterAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——后置通知——" + methodname);
    }

   //環繞通知將決定要不要執行連接點
    public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
        System.out.println("環繞通知,執行代碼前");
        //選擇執行
        point.proceed();
        System.out.println("環繞通知,執行代碼后");
    }
}

時間切面:

public class TimeAdvice {

    public void timeBefore(){
        System.out.println("beforeTime = " + System.currentTimeMillis());
    }

    public void timeAfter(){
        System.out.println("afterTime = " + System.currentTimeMillis());
    }
}

在applicationContext中配置切面:

<context:annotation-config/>
    <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
    <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
    <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
    <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>

    <aop:config>
       <!-- 配置一個切面 -->
       <aop:aspect id="logaop" ref="logAdivice" order="2">
           <!-- 定義切入點,表示對service的所有方法都進行攔截 -->
           <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
           <!-- 定義前置通知 -->
           <aop:before method="myBeforeAdivice" pointcut-ref="testpointcut"/>
           <!-- 定義后置通知 -->
           <aop:after-returning method="myAfterAdivice" pointcut-ref="testpointcut"/>
           <!-- 定義環繞通知 -->
           <aop:around method="myAroundAdivice" pointcut-ref="testpointcut"/>
       </aop:aspect>

       <!-- 定義另一個切面 -->
       <aop:aspect id="timeaop" ref="timeAdvice" order="1">
           <!-- 定義切入點,表示對service的所有方法都進行攔截 -->
           <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
           <!-- 定義前置通知 -->
           <aop:before method="timeBefore" pointcut-ref="testpointcut"/>
           <!-- 定義后置通知 -->
           <aop:after-returning method="timeAfter" pointcut-ref="testpointcut"/>
       </aop:aspect>
    </aop:config>

當有多個切面時,Spring默認是按照切面定義的順序來執行,也可以通過order屬性來配置切面的執行屬性,order=1 早于 order=2執行

測試

public class AOPTest {
    public static void main(String[] args) {
        ApplicationContext context = new 
                            ClassPathXmlApplicationContext("applicationContext.xml");
        TestAOPService service = (TestAOPService) context.getBean("testAOPService");
        service.addUser();
    }
}

二. 基于@AspectJ注解的AOP實現

啟用@AsjectJ支持

在applicationContext.xml中配置下面一句:

<aop:aspectj-autoproxy />

第一步、定義具體業務邏輯模塊(目標對象)

第一步和基于Schema的一樣

TestAOPDaoImpl .java

public class TestAOPDaoImpl implements TestAOPDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }
}

TestAOPServiceImpl.java

public class TestAOPServiceImpl implements TestAOPService{

    @Autowired
    private TestAOPDao testAOPDao;

    @Override
    public void addUser() {
        testAOPDao.addUser();
    }
}

第二步和第三步 定義切面(即實現通知邏輯)

重點是定義切入點

@Aspect
public class LogAdivice{

    //定義一個方法作為切入點id
    @Pointcut("execution(* com.ssh.service.TestAOPService.*(..))")
    private void allMethod(){}

    @Before("allMethod()")
    public void myBeforeAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——前置通知——" + methodname);
    }

    @AfterReturning("allMethod()")
    public void myAfterAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——后置通知——" + methodname);
    }

  //環繞通知將決定要不要執行連接點
    @Around("allMethod()")
    public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
        System.out.println("環繞通知,執行代碼前");
        //執行
        point.proceed();
        System.out.println("環繞通知,執行代碼后");
    }
}

在applicationContext的配置:

<!-- 打開自動掃描(隱式打開注解管理器) -->
    <!-- <context:component-scan base-package="com.ssh"/> -->
    <context:annotation-config/>
    <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
    <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
    <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
    <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>

    <!-- 打開aop注解管理器 -->
    <aop:aspectj-autoproxy/>

參考文章在各個小結中有鏈接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 控制反轉: 把創建對象的權利交給框架,在使用過程中直接去得到這個對象;它包括依賴注入(Dependency Inj...
    _Sisyphus閱讀 245評論 1 1
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • IoC 容器 Bean 的作用域 自定義作用域實現 org.springframework.beans.facto...
    Hsinwong閱讀 2,502評論 0 7
  • 什么是Spring Spring是一個開源的Java EE開發框架。Spring框架的核心功能可以應用在任何Jav...
    jemmm閱讀 16,535評論 1 133
  • 尊敬的閆老師: 您好,看了您的那封信以后我覺得這個活動對我的作文的水平有著很大的幫助。 開始寫的時候,我并不知道自...
    藺佳宇ljy閱讀 282評論 3 2