Spring
[百度1](http://www.baidu.com/" 百度一下")
Spring解決的問題
-
之前代碼耦合性太高,service層要使用dao,要添加相應的dao實作類作為成員變量,但有一天想換實作類,就需要修改源代碼,不符合開閉原則
開閉原則:程式對于拓展是開放的,對修改關閉
- 控制事務繁瑣,spring的AOP,面向切面程式設計,友善的對代碼進行增強。是我們更關注業務邏輯代碼,而不是無關的日志記錄等操作
- 友善內建第三方架構,向上內建表現層,向下內建持久層
Spring 三大概念
-
IoC
控制反轉,把建立對象的權利交給spring去做,我們隻需在使用到的時候去從spring容器中去取。spring通過讀取配置中的元配置對bean對象進行建立
配置的三種方式
- 基于xml配置,古老,結構清晰,但bean對象過多配置會繁瑣。但是有些不能使用注解配置的仍然需要使用xml的配置
- 基于注解,簡單,但是不直覺,較常用
- 基于java代碼配置,在springboot中很常見,通常配合注解進行使用
-
DI
依賴注入,通常DI概念應該包含在Ioc中,是指在建立對象的時候把對象依賴的對象給注入進去
-
AOP
面向切面程式設計
Ioc和DI
Spring測試架構
傳統junit測試是在每一次test去啟動spring容器 ,不僅啟動開銷大,而且每次測試都是非正常關閉容器。
spring測試是在spring容器中去開啟test,會自動關閉spring容器。傳統測試 需要手動去調用容器的關閉操作
Spring 建立對象的四種方式
- 構造器建立(常用)
- 靜态工廠建立,不推薦
- 執行個體工廠建立,不推薦
- 實作FactoryBean接口建立(後面架構會使用)
BeanFactory接口和ApplicationContext接口差別
- BeanFactory隻支援Ioc相關操作,即建立,銷毀和管理bean。同時它是在用到bean的時候才會去建立bean
- applicationContext繼承自beanfactory接口,除了ioc相關操作,還包括Aop和國際化,事務等其他操作。它是在spring容器啟動的時候就把所管理的bean進行建立
bean對象作用域問題
- singleton,單例,在spring容器中僅會存在一個bean執行個體。spring容器關閉,bean會被銷毀
- prototype,多例,每次從容器中取出bean的時候都會建立出一個新的bean。容器關閉,但bean不被銷毀,因為對象并不被spring容器所管理
bean對象的初始化和銷毀(生命周期 )
bean标簽中有init-method和destroy-method.前者在構造器執行後即對象建立後執行,後者會在容器關閉之前執行。注意如果如果bean的scope是prototype,spring容器隻會去建立和初始化對象,這個對象并不會被容器管理,是以即便容器關閉也不會執行bean的銷毀方法
DI注入常用方式(xml)
- 基于setter,使用标簽,類中必須相應的屬性(提供setter)
- 基于構造器,使用标簽,類中必須有相應的構造器
xml中引入其他配置檔案
區分以下兩個
- 引入properties,下面的配置需要使用屬性占位符去取值,比如資料庫連接配接資訊
- 引入其他的spring配置的xml檔案,比如後期會有個總的application-context檔案,裡面會引入其他的service層,dao層配置檔案,友善進行管理
補充:
Spring中有很多命名空間,比如context:和aop:等,背後都有相應的一個處理器去處理和解析這些命名空間,這些處理類都實作了一個接口NamespaceHandler,裡面每個屬性都會有個對應的parser,比如下邊的ContextNamespaceHandler
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
基于注解的配置
-
DI注解配置
在xml中添加context:annotation-config/可省略,但web環境必須添加
-
注入對象
方式一 @[email protected]
方式二 @Resource
-
注入簡單類型資料
@Value,通常很少為某個屬性直接注入常量值,通常用于讀取properties檔案後,将檔案中的key值注入進來,但要注意,解析檔案一定要有<context:property-placeholder location=“classpath:db.properties”/> 配置,或者在javaconfig配置場景下,提供相應的讀取解析類
-
Ioc注解配置
在xml中添加<context:component-scan base-package="">,可以猜測底層一定有個處理器處理該注解
四大Ioc注解@Component,@Repository,@Service,@Controller。四者功能相同,僅是标記不同,不知道屬于哪一層,使用@Component
-
aop注解配置
<aop:aspectj-autoproxy />,這是aop和aspect注解解析器
代理思想
靜态代理
靜态代了解決責任不分離的問題,業務層就應該做業務邏輯相關,日志記錄和權限檢查等不應該由業務層來做。所謂靜态代理就是代理類的位元組碼檔案在程式運作前就确定,即被代理類和代理類的關系在運作前就确定。使用就是寫一個代理類實作和被代理類相同接口,然後再增強,有點像裝飾設計模式,用時直接使用代理類就行。靜态代理存在很多問題,針對每個被代理類都需要去寫一個代理類,而且代理類中每個方法如果都有相同的代碼,每個方法又要重複相同操作,是以是二次備援,配置繁瑣,代碼備援,一般不用,期待動态代理。
動态代理
- 代理類是在運作期間jvm通過反射建立的,就是說在運作前根本不存在代理類的位元組碼檔案,代理類和被代理類的關系是在運作期間才建立的
- 注意此處配置相應處理器而不是直接配置代理類,代理方式是開發一個handler實作InvocationHandler,這個handler中會包含真實對象和增強對象比如tx,分為jdk動态代理(基于接口)和cglib動态代理(基于繼承)。那麼代理對象從這個處理器中的一個方法來獲得,方法名自己定。那相當于配置時,僅需要配置handler,然後通過handler獲得代理對象,然後使用時隻需要代理對象去調用相應的方法即可
- 選用:有接口使用jdk動态代理,沒有使用cglib動态代理
-
怎麼了解動态代理過程:
TxManagerHandler實作InvocationHandler接口,覆寫invoke方法,且類中有增強類對象和真實對象。invoke方法中自己去調用增強類中的方法去增強。另外需要自己寫一個方法傳回代理對象,方法名随意比如getProxy(),然後jdk動态代理和cglib方式建立過程不同,但最終會把代理對象傳回,而且建立時有個特點都會把目前handler對象也就是this傳進去用于回調。通過反編譯看出動态建立的代理類,會發現此代理類實作了和真實對象實作的相同接口(IemployeeService),裡面的save和update方法調用的就是InvocationHandler對象的invoke方法,也就是TxManagerHandler類中我們實作的invoke方法.
-
jdk動态代理完整開發:
配置檔案
<bean name="txManager" class="cn.wolfcode.common.tx.TxManager" />
<bean name="employeeService" class="cn.wolfcode.common.service.EmployeeServiceImpl" />
<bean name="txManagerHandler" class="cn.wolfcode.jdkproxy.TxManagerHandler">
<property name="tx" ref="txManager" />
<property name="target" ref="employeeService" />
</bean>
自定義事務類
public class TxManager {
public void open(){
System.out.println("開啟事務。。。");
}
public void commit(){
System.out.println("送出事務。。。");
}
public void rollback(){
System.out.println("復原事務。。。");
}
public void close(){
System.out.println("關閉資源。。。");
}
}
service(被代理對象)
public class EmployeeServiceImpl implements IEmployeeService{
public void insert(String name, String password) {
System.out.println("儲存員工");
}
public void update(String name, String password) {
int a = 1 / 0;
System.out.println("修改員工");
}
}
動态代理類
public class TxManagerHandler implements InvocationHandler{
private TxManager tx; //自定義事務增強對象
private IEmployeeService target; //需要增強的對象(被代理對象)
public void setTx(TxManager tx) {
this.tx = tx;
}
public void setTarget(IEmployeeService target) {
this.target = target;
}
/*
* 傳回代理對象
*/
public <T>T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/*
* 真正執行的方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
try {
tx.open();
obj = method.invoke(target, args);
tx.commit();
} catch (Exception e) {
// TODO: handle exception
tx.rollback();
}finally {
tx.close();
}
return obj;
}
}
-
cglib動态代理完整開發(其餘相同,動态代理類不同):
cglib動态代理類
public class TxManagerHandler implements InvocationHandler{
//事務管理器
private TxManager tx;
//真實對象
private IEmployeeService target;
public void setTx(TxManager tx) {
this.tx = tx;
}
public void setTarget(IEmployeeService target) {
this.target = target;
}
/*
* 傳回代理對象
*/
public <T>T getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return (T) enhancer.create();
}
/*
* 真正執行的方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
try {
tx.open();
obj = method.invoke(target, args);
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
tx.close();
}
return obj;
}
}
測試儲存
@Test
public void testSave() throws Exception {
IEmployeeService service = txManagerHandler.getProxy();
System.out.println(service.getClass());
service.insert("sss", "666");
}
結果:
開啟事務。。。
儲存員工
送出事務。。。
關閉資源。。。
測試更新:裡面故意抛出異常,測試復原
@Test
public void testUpdate() throws Exception {
IEmployeeService service = txManagerHandler.getProxy();
service.update("sss", "7777");
}
結果:
開啟事務。。。
復原事務。。。
關閉資源。。。
-
怎麼用
我們建立的是TxManagerHandler類,那麼配置就是配置這個類的對象,然後把真實對象和增強對象依賴注入進來。在真正用到時把handler對象注入,然後handler對象.getProxy()擷取代理對象,用真實對象的接口接收即可,因為代理對象和真實對象都實作了相同的接口.然後就可以正常調用service.save()方法即可
IEmployeeService service = txManagerHandler.getProxy();
-
缺陷
首先動态代理可以解決代理類中方法中重複代碼的問題,但無法解決每個類都需要一個代理類的問題,配置檔案中仍需要為每一個被代理類進行配置處理器,也就是說handler類我們隻需要寫一個而且是通用的,但如果每一個被代理類想要用這個handler就要再去配一次,很不友好,期待AOP。
補充:
jvm如何在運作期間動态的建立一個類呢。在運作期間通過位元組碼檔案(.class)的格式和結構,生成相應二進制資料,然後二進制資料轉成相應的類
AOP
首先AOP的底層原理就是動态代理,Spring預設是jdk動态代理,我們也可以改為cglib動态代理
面向切面程式設計,我們想做日志增強,就把日志增強做成一個切面,可以去切多個類中的多個方法,那麼這些被切的類中就有了日志增強,這樣就不需要動态代理那樣為每個類都配置一個handler進行增強
常用術語
- JoinPoint: 連接配接點,就是那些被增強的方法。通過JoinPoint和其子類ProceedingJoinPoint中的某些方法,可以去執行被增強的方法和擷取被增強方法的相關資訊比如簽名參數等,ProceedingJoinPoint隻能作為around方法的參數
- PointCut: 切入點,表示where,有多個連接配接點構成。表示對哪些類中的哪些方法進行增強.一般用Aspectj中的表達式來表達
- Advice:增強,when+what,表示了在什麼時候做什麼樣的增強
- Aspect:切面,Aspect = Pintcut+Advice,在哪些地方在什麼時機做什麼增強
配置方式
-
XML配置
注意使用AOP,那些在Spring容器中管理的bean都不再是真實類型了,而是AOP為每個真實類型生成的代理類型。而且隻要配置下邊這些,在Spring容器時,容器就為每個被切到的類生成了代理類,是以整個應用下來拿到的都是代理對象不是真實對象
<bean name="txManager" class="cn.wolfcode.common.tx.TxManager" />
<aop:config>
<!-- ref:表示做什麼增強,此處是日志增強-->
<aop:aspect ref="txManager">
<!-- pointcut: 表示對哪些類中哪些方法做增強-->
<aop:pointcut expression="execution(* cn.wolfcode.common.service.*ServiceImpl.*(..))" id="txPointCut"/>
<!-- 下邊表示在什麼時候增強,注意每個都要聯系切點pointcut-ref,可以了解,因為上述切點可以定義多個->
<!--前置增強-->
<aop:before method="open" pointcut-ref="txPointCut"/>
<!--後置增強,正常執行後做的增強-->
<aop:after-returning method="commit" pointcut-ref="txPointCut"/>
<!--異常增強-->
<aop:after-throwing method="rollback" throwing="ex" pointcut-ref="txPointCut"/>
<!--最終增強,無論有無異常,都要做的增強,比如關閉資源-->
<aop:after method="close" pointcut-ref="txPointCut"/>
<!--環繞增強,包含以上幾個-->
<aop:around method="around" pointcut-ref="txPointCut"/>
</aop:aspect>
</aop:config>
-
注解配置
一定在xml中加入<aop:aspectj-autoproxy />,這是aop和aspect注解解析器
注解配置需要我們額外開發一個Advice類,一般起名xxAdvice.貼上@Aspect标簽和@Component,然後寫個方法貼上@Pointcut标簽,表示where,下邊的方法貼上對應時間的标簽,具體如下 :
@Component
@Aspect
public class TxManagerAdvice {
@Pointcut("execution(* cn.wolfcode.common.service.*ServiceImpl.*(..))")
public void txPoint(){}
@Before("txPoint()")
public void open(){
System.out.println("開啟事務。。。");
}
}