一. 程式的耦合
-
耦合 : 程式間的依賴關系
包括 :
- 類之間的依賴
- 方法間的依賴
- 解耦 : 降低程式間的依賴關系
- 實際開發 : 應該做到
編譯器不依賴, 運作期才依賴
- 解耦的思路 :
- 使用反射的方式建立對象, 而避免使用new關鍵字
- 通過讀取配置檔案來擷取要建立的對象全限定類名
IoC(控制翻轉) : Inversion of Control, 把建立對象的權利交給架構, 是架構的重要特征, 并非面向對象程式設計的專用術語. 它包括依賴注入(Dependency Injection, 簡稱DI) 和 依賴查找(Dependency Lookup)
Ioc的作用 :
削減計算機程式的耦合(解除我們代碼中的依賴關系).
/**
業務層調用持久層
IoC(控制反轉)
将控制權交給工廠,來幫我們建立對象. 帶來的好處就是 降低了程式間的依賴關系, 也叫削減計算機的耦合
*/
// private AccountDao accountDao = new AccountDaoImpl();
private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
二. 使用Spring的IOC解決程式的耦合
2.1 xml方式
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把對象的建立交給spring來管理-->
<bean id="accountService" class="top.clearlight.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="top.clearlight.dao.impl.AccountDaoImpl"></bean>
</beans>
擷取Spring的IoC核心容器, 并根據id擷取對象
ApplicationContext
的三個常用實作類 :
-
: 它可以加載類路徑下的配置檔案, 要求配置檔案必須在類路徑下. 不在的話, 加載不了ClassPathXmlApplicationContext
-
: 它可以加載磁盤任意路徑下的配置檔案(必須有通路權限)FileSystemXMLApplicationContext
-
: 它是用于讀取注解建立容器的AnnotationConfigApplicationContext
核心容器的兩個接口 :
-
ApplicationContext
: (單例對象使用)(開發使用更多)
它在建構核心容器時, 建立對象采取的政策是采用立即加載的方式. 也就是說, 隻要一讀取完配置檔案馬上就建立配置檔案中配置的對象.
// 1. 擷取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// applicationcontext ac = new filesystemxmlapplicationcontext("c:\\users\\87052\\desktop\\bean.xml");
// 2. 根據id擷取Bean對象
AccountService as = (AccountService) ac.getBean("accountService");
AccountDao ad = ac.getBean("accountDao", AccountDao.class);
-
BeanFactory
: (多例對象适用)
它在構件核心容器時, 建立對象采取的政策是采用延遲加載的方式. 也就是說, 什麼時候根據id擷取對象了, 什麼時候才真正的建立對象.
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
AccountService as1 = (AccountService) factory.getBean("accountService");
spring對bean的管理細節
- 建立bean的三種方式
-
使用預設構造函數建立
在spring的配置檔案中使用bean标簽, 配以id和class屬性之後, 且沒有其他屬性和标簽時, 采用的就是預設構造函數建立bean對象, 此時如果類中沒有預設構造函數, 則對象無法建立.
<!-- 使用預設構造函數建立對象 -->
<bean id="accountService" class="top.clearlight.service.impl.AccountServiceImpl"></bean>
- 使用普通工廠中的方法建立對象(使用某個類中的方法建立對象, 并存入spring容器)
/**
*模拟一個工廠類(該類可能存在于jar包中, 我們無法通過修改源碼的方式來提供預設構造函數)
*/
public class InstanceFactory {
public AccountService getAccountService() {
return new AccountServiceImpl();
}
}
<bean id="instanceFactory" class="top.clearlight.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
- 使用工廠中的靜态方法建立對象(使用某個類中的靜态方法建立對象, 并存入spring容器)
/**
* 模拟一個工廠類,其中建立對象的方法是靜态的
*/
public class StaticFactory {
public static AccountService getAccountService() {
return new AccountServiceImpl();
}
}
<bean id="accountService" class="top.clearlight.factory.StaticFactory" factory-method="getAccountService"></bean>
Tips : 第二種和第三種的xml配置方式不同的原因和真實調用方法的不同是類似的. 普通方法需要通過new該類後再去調用類中普通方法, 而靜态方法(第三種)可以直接通過類名來直接調用.
-
bean對象的作用範圍
bean标簽的scope屬性 :
- 作用 : 用于指定bean的作用範圍
- 取值 : 常用的就是單例的和多例的
- singleton : 單例的(預設值)
- prototype : 多例的
- request : 作用于web應用的請求範圍
- session : 作用于web應用的會話範圍
- global-session : 作用于叢集環境的會話範圍(全局會話範圍), 當不是叢集環境時, 它就是session
- bean對象的生命周期
- 單例對象 (立即)
- 出生 : 當容器建立時對象出生
- 活着 : 隻要容器還在, 對象一直活着
- 死亡 : 容器銷毀, 對象消亡
- 總結 : 單例對象的生命周期和容器相同
- 多例對象 (延遲)
- 出生 : 當我們使用對象時spring架構為我們建立
- 活着 : 對象隻要是在使用過程中就一直活着
- 死亡 : 當對象長時間不用, 且沒有别的對象引用時, 由Java的垃圾回收器回收
-
Spring的依賴注入
依賴注入 : Dependency Injection
IoC的作用 : 降低程式間的耦合 (依賴關系)
依賴關系的管理 : 都交給Spring的維護
在目前類需要用到其他類的對象, 由Spring為我們提供, 我們隻需要在配置檔案中說明
依賴關系的維護 : 就稱之為依賴注入
-
依賴注入
能注入的資料 :
- 基本類型和String
- 其他bean類型(在配置檔案中或者注解配置過的bean)
- 複雜類型/集合類型
注入的方式 :
-
使用構造函數提供
使用的标簽 :
constructor-arg
标簽出現的位置 : bean标簽的内部
标簽中的屬性 :
- type : 用于指定要注入的資料的資料類型, 該資料類型也是構造函數中某個或某些參數的類型
- index : 用于指定要注入的資料給構造函數中指定索引位置的參數指派. 索引的位置是從0開始
- name : 用于指定給構造函數中指定名稱的參數指派
- ---------以上三個用于指定給構造參數中哪個參數指派----------
- value : 用于提供基本類型和String類型的資料
- ref : 用于指定其他的bean類型資料. 它指的就是在Spring的Ioc核心容器中出現過的bean對象 (引用關聯的bean對象)
<bean id="accountService" class="top.clearlight.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="jack"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!-- 配置一個日期對象 -->
<bean id="now" class="java.util.Date"></bean>
優勢 : 在擷取bean對象時, 注入資料是必須的操作, 否則對象無法建立成功.
弊端 : 改變了bean對象的執行個體化方式, 使我們在建立對象時, 如果用不到這些資料, 也必須提供.
-
使用set方法提供 (常用)
首先在需要建立對象的類中為每個字段生成set方法!
使用的标簽 :
property
出現的位置 : bean标簽的内部
标簽的屬性 :
- name : 用于指定注入時所調用的set方法名稱
- value : 用于提供基本類型和String類型的資料
- ref : 用于指定其他的bean類型資料. 它指的就是在Spring的IoC核心容器中出現過的bean對象
<bean id="accountService1" class="top.clearlight.service.impl.AccountServiceImpl">
<property name="name" value="Mike"/>
<property name="age" value="17"/>
<property name="birthday" ref="now"/>
</bean>
<bean id="now" class="java.util.Date"/>
優勢 : 建立對象時沒有明确的限制, 可以直接使用預設構造函數
弊端 : 如果有某個成員必須有值, 則擷取對象是有可能set方法沒有執行
- 複雜類型/集合類型通過set方法依賴注入
首先建立集合類型變量
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties myPro;
并生成對應的set方法,開始通過bean.xml依賴注入
<!--複雜類型的注入/集合類型的注入-->
<bean id="accountService3" class="top.clearlight.service.impl.AccountServiceImpl2">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="jack" value="18"/>
<entry key="Marry" value="16"/>
<entry key="Bob">
<value>19</value>
</entry>
</map>
</property>
<property name="myPro">
<props>
<prop key="A">AAA</prop>
<prop key="B">BBB</prop>
</props>
</property>
</bean>
用于給List結構集合注入的标簽 : list array set
用于給Map結構集合注入的标簽 : map props
結構相同, 标簽可以互換
- 使用注解提供
2.2注解方式
曾經XML的配置 :
<bean id="accountService" class="top.clearlight.service.impl.AccountServiceImpl" scope="" init-method="" destory-method="">
<property name="" value="" / ref=""></property>
</bean>
- 用于建立對象的
它們的作用就和在XML配置檔案中編寫一個 <bean>
标簽實作的功能是一樣的
-
Component
:
作用 : 用于把目前類對象存入spring容器中
屬性 : value : 用于指定bean的id. 當不寫時, 它的預設值是目前類名,且首字母改小寫
-----以下三個注解它們的作用和屬性與Component是一模一樣.----
它們三個是Spring架構為我們提供明确的三層使用的注解, 使我們的三層對象更加清晰
Controller
: 一般用在表現層
Service
: 一般用在業務層
: 一般用在持久層Repository
- 用于注入資料的
它們的作用就和XML配置檔案中的bean标簽中寫一個 <property>
标簽的作用是一樣的
-
:Autowired
-
作用 : 自動按照類型注入. 隻要容器中有唯一的一個bean對象類型和要注入的變量類型比對, 就可以注入成功.
如果IoC容器中沒有任何bean的類型和要注入的變量類型比對, 則報錯.
如果IoC容器中有多個類型比對時 :
- 出現位置 : 可以是變量上, 也可以是方法上
- 細節 : 在使用注解注入時, set方法就不是必須的了.
-
:Qualifier
- 作用 : 在按照類中注入的基礎之上在按照名稱注入. 它再給類成員注入時不能單獨使用. …
- 屬性 : value 用于指定注入bean的id
-
:Resource
- 作用 : 直接按照bean的id注入, 它可以獨立使用
- 屬性 : name 用于指定bean的id
- ----- 以上三個注入都隻能注入其他bean類型的資料, 而基本類型和String類型無法使用上述注解實作. 另外, 集合類型的注入隻能通過xml來實作
-
:Value
- 作用 : 用于注入基本類型和String類型的資料
-
屬性 : value 用于指定資料的值. 它可以使用Spring中的SpEL(也就是Spring的el表達式)
SpEL的寫法 : ${表達式}
- 用于改變作用範圍的
它們的作用就和在bean标簽中使用scope屬性實作的功能是一樣的
-
:Scope
- 作用 : 用于指定bean的作用範圍
- 屬性 : value 指定範圍的取值. 常用取值 : singleton prototype
- 和生命周期相關
它們的作用就和bean标簽中使用init-method和destory-method的作用是一樣的
-
:PreDestory
- 作用 : 用于指定銷毀方法
-
:PostConstruct
- 作用 : 用于指定初始化方法
Spring注解的xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!--把對象的建立交給spring來管理-->
<context:component-scan base-package="top.clearlight"></context:component-scan>
</beans>
最簡單的配置方式 : 存在于jar包中的類, 使用xml的形式
自己寫的, 注解更友善
三. 動态代理
特點 : 位元組碼随用随建立, 随用随加載
作用 : 不修改源碼的基礎上對方法增強
分類 :
- 基于接口的動态代理
- 基于子類的動态代理
基于接口的的動态代理 :
涉及的類 : Proxy
提供者 : JDK官方
如何建立代理對象 ;
使用Proxy類中的
newProxyInstance
方法
建立代理對象的要求 :
被代理類最少實作一個接口, 如果沒有則不能使用
newProxyInstance
方法的參數 :
-
ClassLoader : 類加載器
它是用于加載代理對象位元組碼的. 和被代理對象使用相同的類加載器. 固定寫法.
-
Class[] :
它是用于讓代理對象和被代理對象有相同的方法. 固定寫法.
-
InvocationHandler :
它是讓我們寫如何代理. 我們一般都是寫一個該接口的實作類, 通常情況下都是匿名内部類, 但不是必須的
4. AOP(面向切面程式設計)
Aspect Oriented Programming
作用:
在程式運作期間, 不修改源碼對已有方法進行增強
優勢:
- 減少重複代碼
- 提高開發效率
- 維護友善
關于代理的選擇 :
在Spring中, 架構會根據目标類是否實作了接口來決定采用哪種動态代理的方式
4.1 AOP相關術語
連接配接點(
Joinpoint
) : 業務層接口中所有的方法
連接配接業務和增強方法的點
切入點(
Pointcut
) : 被增強的方法
Advice(通知/增強): 攔截到連接配接點之後所要做的事情就是通知(提供了公共代碼的類)
Target(目标對象) : 代理的目标對象(業務層service)
Weaving(織入) : 原有的Service沒法實作事物的支援, 然後使用動态代理技術建立了一個新的對象, 傳回了一個代理對象. 傳回代理對象的過程中, 其中加入了事物的支援, 加入事物支援的過程, 叫織入.
Proxy(代理) : 一個類被AOP織入增強後, 就産生一個結果代理類
(AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {})
Aspect(切面) : 是切入點和通知(引介) 的結合.
建立切入點方法和通知方法執行調用的對應關系就是切面
Spring中AOP運作階段(Spring架構完成的)
Spring架構監控切入點方法的執行. 一旦監控到切入點方法被運作, 使用代理機制, 動态建立目标對象的代理對象, 根據通知類别, 在代理對象的對應位置, 将通知對應的功能織入, 完成完整的代碼邏輯運作.
4.2 Spring中基于XML的AOP配置
- 把通知Bean也交給Spring來管理
- 使用aop:config标簽表名開始AOP的配置
- 使用aop:aspect标簽表名配置切面
- id屬性: 是給切面提供一個唯一辨別
- ref屬性: 是給指定通知類bean的id
-
在aop:aspect标簽的内部使用對應标簽來配置通知的類型
我們現在示例是讓printLog方法在切入點方法執行之前: 是以是前置通知
aop:before: 表示配置前置通知
method屬性: 用于指定Logger類中哪個方法是前置通知
pointcut屬性: 用于指定切入點表達式, 該表達式的含義指的是對業務層中哪些方法增強
切入點表達式的寫法:
- 關鍵字: execution(表達式)
- 表達式: 通路修飾符 傳回值 包名.包名.包名…類名.方法名(參數清單)
-
标準的表達式寫法:
public void top.clearlight.service.impl.AccountServiceImpl.saveAccount()
-
通路修飾符可以省略
void top.clearlight.service.impl.AccountServiceImpl.saveAccount()
-
傳回值可以使用通配符, 表示任意傳回值
* top.clearlight.service.impl.AccountServiceImpl.saveAccount()
-
包名可以使用通配符, 表示任意包. 但是有幾級包, 就需要寫幾個*
* *.*.*.*.AccountServiceImpl.saveAccount()
-
包名可以使用…表示目前包及其子包
* *..AccountServiceImpl.saveAccount()
-
類名和方法名都可以使用*來實作通配
* *..*.*()
-
參數清單:
可以直接寫資料類型:
- 基本資料類型直接寫名稱 int
- 引用類型寫包名.類名的方式 java.lang.String
可以使用通配符表示任意類型, 但是必須有參數
可以使用…表示有無參數均可, 有參數可以是任意類型
-
全通配寫法:
* *..*.*(..)
-
實際開發中切入點表達式的通常寫法:
切到業務層實作類下的所有方法:
top.clearlight.service.impl.*.*(..)
-
環繞通知
問題: 當我們配置了環繞通知之後, 切入點方法沒有執行, 而通知方法執行了.
分析: 通過對比動态代理中的環繞通知代碼, 發現動态代理的環繞通知有明确的切入點方法調用, 而我們的代碼中沒有.
解決: Spring架構為我們提供了一個接口: ProceedingJoinPoint 該接口有一個方法proceed(), 此方法就相當于明确調用切入點方法.
該接口可以作為環繞通知的方法參數, 在程式執行時, Spring架構回味我們提供該接口的實作類供我們使用.
Spring中的環繞通知:它是Spring架構為我們提供的一種可以在代碼中手動控制增強方法何時執行的方式.
bean.xml的環繞通知配置:
<aop:config>
<!-- 配置切入點表達式 id屬性用于指定表達式的唯一辨別. expression屬性用于指定表達式内容
此标簽寫在aop:aspect标簽内部隻能目前切面使用.
它還可以寫在aop:aspect外面, 此時就變成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* top.clearlight.service.impl.*.*(..))"></aop:pointcut>
<aop:aspect id="logAdvice" ref="logger">
<!--配置環繞通知-->
<aop:around method="aroundPrintLog" point-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
Object[] args = pjp.getArgs(); //得到方法執行所需的參數
System.out.println("Logger類中的aroundPrintLog方法開始記錄日志.. 前置");
rtValue = pjp.proceed(args); // 明确調用業務層方法(切入點方法)
System.out.println("Logger類中的aroundPrintLog方法開始記錄日志.. 後置");
return rtValue;
} catch (Throwable e) {
System.out.println("Logger類中的aroundPrintLog方法開始記錄日志.. 異常");
throw new RuntimeException(e);
} finally {
System.out.println("Logger類中的aroundPrintLog方法開始記錄日志.. 最終");
}
}