[TOC]
Spring概述
Spring體系結構
- core container
- beans與core 它們提供spring框架最基本功能,包含ioc與di
- context 上下文物件,基於beans與cores:做了一些封裝,讓我們可以更方便的使用實現ioc
- spel它是sprng提供的一個表示式語言
- Data access/integration
- 資料訪問
- 整合
- Web
- Spring本身提供spring mvc
- 也可以與其它的web層進行整合
- AOP
- AOP大部分情況下是使用動態代理來實現的。
- Test
- 使用spring可以方便的進行測試
Spring框架優點
- 方便解耦,簡化開發 Spring就是一個大工廠,可以將所有物件建立和依賴關係維護,交給Spring管理
- AOP程式設計的支援 Spring提供面向切面程式設計,可以方便的實現對程式進行許可權攔截、執行監控等功能
- 宣告式事務的支援 只需要通過配置就可以完成對事務的管理,而無需手動程式設計
- 方便程式的測試 Spring對Junit4支援,可以通過註解方便的測試Spring程式
- 方便整合各種優秀框架 Spring不排斥各種優秀的開源框架,其內部提供了對各種優秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支援
- 降低JavaEE API的使用難度 Spring 對JavaEE開發中非常難用的一些API(JDBC、JavaMail、遠端呼叫等),都提供了封裝,使這些API應用難度大大降低
IOC與DI
Ioc它是什麼,解決什麼問題,它的原理是如何實現?
IOC:inversion of Controller:控制反轉:它主要解決程式碼之間耦合度過高的問題。
在程式中所說的IOC其實簡單說,就是原來由我們自己例項化的物件交給spring容器來實始化。這時物件的實始化的權利就會反轉。
DI:dependency injection:依賴注入:為物件的屬性賦值,清除物件與物件之間的依賴關係。
在spring框架負責建立Bean物件時,動態將依賴物件注入到Bean元件。
Bean獲取與例項化
ApplicationContext與BeanFactory關係
BeanFactory它採取延遲載入的方案,只有真正在getBean()時才會例項化Bean。
ApplicationContext它是擴充套件BeanFactory介面。在開發中我們一般使用的是ApplicationContext,真正使用的是其實現類。
- FileSystemXmlAppliCationContext 根據檔案路徑獲取
- ClassPathXmlApplicationContext 根據類路徑獲取
AppliCationContext它會在配置檔案載入時,就會初始化Bean,並且ApplicationContext它提供不同的應用層的Context實現。例如在web開發中可以使用WebApplicationContext.
Bean的例項化方式
無引數構造 對於這種方式,注意Bean類中必須提供無引數構造。
靜態工廠方法 需要建立一個工廠類,在工廠類中提供一個static返回bean物件的方法就可以。
例項工廠方法 需要建立一個工廠類,在工廠類中提供一個非static的建立bean物件的方法,在配置檔案中需要將工廠配置,還需要配置bean
<!-- 在開發中可以使用id或name id它遵守的xml規範,名稱中不能包含特殊符號 name它是為了更好的擴充套件,在name中可以包含一些特殊符號,例如"/" -->
<bean name="bean1" class="com.lbb.bean.Bean1"></bean>
<bean name="bean2" class="com.lbb.bean.Bean2Factory" factory-method="getBean2"></bean>
<bean name="bean3Factory" class="com.lbb.bean.Bean3Factory"></bean>
<bean name="bean3" factory-bean="bean3Factory" factory-method="createBean3" scope="prototype"></bean>
複製程式碼
public class Bean2Factory {
public static Bean2 getBean2(){
return new Bean2();
}
}
public class Bean3Factory {
public Bean3 createBean3(){
return new Bean3();
}
}
複製程式碼
Bean的作用域
在bean宣告時它有一個scope屬性,它是用於描述bean的作用域。可取值有
- singleton:單例 代表在spring ioc容器中只有一個Bean例項 (預設的scope)
- prototype多例 每一次從spring容器中獲取時,都會返回一個新的例項
- request 用在web開發中,將bean物件request.setAttribute()儲存到request域中(一次請求一次?)
- session 用在web開發中,將bean物件session.setAttribute()儲存到session域中(一次會話一次?)
- 還有一個globalSession:用於分散式開發的時候全域性Session。
Bean的生命週期
- instantiate bean物件例項化 . populate properties 封裝屬性 . 如果Bean實現BeanNameAware執行setBeanName . 如果Bean實現BeanFactoryAwar或ApplicationContextAwar設定工廠setBeanFactory或上下文物件setApplicationContext . 如果存在類實現BeanPostProcessor(後處理Bean),執行postProcessBeforeInitialization . 如果Bean實現InitializingBean執行afterPropertiesSet . 呼叫自定義的init-method方法 . 如果存在類實現BeanPostProcessor(處理Bean),執行postProcessAfterInitialization . 執行業務處理
- 如果Bean實現DisposableBean執行destroy
- 呼叫自定義的destroy-method
對於bean的生命週期方法
第三步與第四步是讓Bean瞭解spring容器。
第五步與第八步 可以針對指定的Bean進行功能增強,這時一般會使用動態代理。
第六步與第十步:通過實現指定的介面來完成init與destroy操作 但是在開發中一般不使用第6步與第10步,原因是我們可以使用第7步與第11步來完成。
第7步與第11步的初始化與銷燬操作它無耦合,推薦使用的。但是必須在配置檔案中指定初始化與銷燬的方法。
<bean name="beanLifeTest" class="com.lbb.bean.BeanLife" init-method="myInit" destroy-method="myDestroy">
<property name="name" value="張三"></property>
</bean>
<bean class="com.lbb.bean.MyBeanPostProcess"></bean>
複製程式碼
package com.lbb.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class BeanLife implements BeanNameAware,ApplicationContextAware,InitializingBean,
DisposableBean,TestBeanLife{
private String name;
public BeanLife() {
System.out.println("第一步:例項化BeanLife物件");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("第二步:屬性name的注入" + name);
}
@Override
public void setBeanName(String name) {
System.out.println("第三步:得到bean的id或name值:" + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("第四步:得到ApplicationContext物件:" + applicationContext);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("第六步:屬性注入完成後");
}
public void myInit() {
System.out.println("第七步:自定義的init方法");
}
@Override
public void test(){
System.out.println("第九步:test--------------------test");
}
@Override
public void destroy() throws Exception {
System.out.println("第十步:執行destroy方法");
}
public void myDestroy() {
System.out.println("第十一步:執行自定義的銷燬方法");
}
}
複製程式碼
package com.lbb.bean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcess implements BeanPostProcessor{
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第八步:beanPostProcessor的after方法");
if (beanName.equals("beanLifeTest")) {
// 使用動態代理可以處理bean的功能增強
Object proxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("test")) {
System.out.println("增強Test");
}
return method.invoke(bean, args);
}
});
return proxy;
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:beanPostProcessor的before方法");
System.out.println("處理的bean是" + bean + " 它的名稱是" + beanName);
return bean;
}
}
複製程式碼
總結:對於bean的生命週期,我們需要關注的主要有兩個方法。
- 增強bean的功能可以使用後處理Bean, BeanPostProcessor . 如果需要初始化或銷燬操作我們可以使用init-method destroy-method
注意:destroy-method只對scope=singleton有效果。(呼叫application.close方法才可以)
Bean的屬性注入
<!-- 使用構造方法對car的屬性進行注入 不常用-->
<bean name="car" class="com.lbb.di.Car">
<constructor-arg index="0" type="java.lang.String" value="賓士"></constructor-arg>
<constructor-arg index="1" type="java.lang.Double" value="1000.0"></constructor-arg>
</bean>
<bean name="car2" class="com.lbb.di.Car">
<constructor-arg type="java.lang.Double" value="2000.0"></constructor-arg>
<constructor-arg type="java.lang.String" value="寶馬"></constructor-arg>
</bean>
<!-- 使用setter方法對car的屬性進行注入 常用 -->
<bean name="car3" class="com.lbb.di.Car">
<property name="name" value="吉普"></property>
<property name="price" value="2222"></property>
</bean>
<bean name="person4" class="com.lbb.di.Person">
<property name="name" value="張三"></property>
<property name="car" ref="car1"></property>
</bean>
<!-- 集合屬性注入(陣列注入的方法與list一樣) -->
<bean name="collectionDemo" class="com.lbb.di.CollectionDemo">
<property name="list">
<list>
<value>張三</value>
<value>10</value>
<ref bean="car2"/>
</list>
</property>
<property name="set">
<set>
<value>李四</value>
<value>20</value>
<ref bean="person4"/>
</set>
</property>
<property name="map">
<map>
<entry key="name" value="張三"></entry>
<entry key-ref="person4" value-ref="car1"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="姓名">張三</prop>
<prop key="年齡">30</prop>
</props>
</property>
</bean>
複製程式碼
名稱空間p和c的使用
Spring2.0以後提供了xml名稱空間。
首先它們不是真正的名稱空間,是虛擬的。它是嵌入到spring核心中的。
使用p名稱空間可以解決我們setter注入時<property>
簡化
使用c名稱空間可以解決我們構造器注入時<constructor-arg>
簡化
spring expression language 是在spring3.0以後的版本提供 它類似於ognl或el表示式,它可以提供在程式執行時構造複雜表示式來完成物件屬性儲存及方法呼叫等。
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- <bean id="dog1" class="cn.itheima.namsapce.Dog"> <property name="name"
value="吉娃娃" /> <property name="color" value="黃色"></property> </bean> -->
<!-- 使用p名稱空間來簡化 上述操作 -->
<bean id="dog1" class="cn.itheima.namsapce.Dog" p:name="博美"
p:color="白色"></bean>
<!-- <bean id="dog2" class="cn.itheima.namsapce.Dog"> <constructor-arg index="0"
value="黑色" /> <constructor-arg index="1" value="大丹" /> </bean> -->
<!-- 使用c名稱空間來簡化 上述操作 -->
<bean id="dog2" class="cn.itheima.namsapce.Dog" c:name="大丹"
c:color="黑色"></bean>
<bean id="person" class="cn.itheima.namsapce.Person" p:name="張三"
p:dog-ref="dog2" p:age="20">
</bean>
<bean id="person1" class="cn.itheima.namsapce.Person">
<property name="name" value="#{person.name}" />
<!-- <property name="dog" ref="dog1"/> -->
<property name="dog" value="#{dog1}"></property>
<property name="age" value="#{person.getAge()+10}"></property>
</bean>
</beans>
複製程式碼
Spring註解開發
想要使用註解開發,首先匯入spring-aop-....jar
包,使用<context:component-sacn base-package=””>
配置包掃描。
常用註解
- @Component
- @Repository 用於DAO層
- @Service 用於service層
- @Controller 用於表現層 對於我們的bean所處在的位置可以選擇上述三個註解來應用,如果你的bean不明確位置,就可以使用@Component.
- @Value
- @Autowired:它預設是根據型別進行注入。如果與@Qualifier一起使用,就可以根據名稱來進行注入。
- @Resource 注意:@Value @Autowired它們可以修飾屬性,也可以修飾setter方法,如果寫在屬性上,就不需要提供setter方法。
- @Scope它以描述bean的作用域。
- @PostConstruct:它相當於init-method=”myInit
- @PreDestroy:它相當於是destroy-method=”myDestroy”。注意:對於銷燬的方法它只對bean的scope=singleton有效。
Spring在web開發中的應用
- 作用:能讓我們直接在web應用中直接使用Spring,而不是每個用到Spring的地方都去建立Spring容器(application)
- 原理:匯入Spring-web.jar之後,當我們在web.xml檔案中配置Spring的ContextLoaderListener(這個Listener實際上是一個ServletContextListener)後,專案一啟動這個listener裡面的程式碼就會執行,在listener裡面就會幫我們去載入Spring的核心配置檔案applicationContext.xml,建立一個WebApplicationContext(ApplicationContext的實現類), 並且將WebApplicationContext物件儲存在ServletContext裡面,以後我們使用要從Spring容器裡面獲取物件的時候,直接從ServletContext物件裡面取出WebApplicationContext物件就可以,就不需要每次都去建立WebApplicationContext,載入配置檔案了。
實現步驟
在web專案中要使用spring需要匯入一個jar包spring-web-...jar
在web.xml檔案中配置Listener。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
複製程式碼
這個ContextLoaderListener它實現了ServletContextListener。在這個listener中,當伺服器啟動時,將ApplicationContext物件,其實是它的一個實現類WebApplicationContext物件存入到了ServletContext中。
我們還需要在web.xml檔案中配置applicationContext.xml檔案的位置。
預設情況下會在WEB-INF目錄 下查詢applicationContext.xml, 如果applicationContext.xml檔案不是在預設位置,我們可以在web.xml檔案中配置。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
複製程式碼
Spring整合junit4測試
導包spring-test-...jar
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class AnnotationTest {
@Autowired
private IUserService userService;
@Test
public void test1(){
}
}
複製程式碼
Spring AOP
AOP概述
在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
AOP是一個概念,並沒有設定具體語言的實現,它能克服那些只有單繼承特性語言的缺點,spring2.0之後整合AspectJ第三方AOP技術。
AspectJ是一個面向切面的框架,它擴充套件了Java語言。AspectJ定義了AOP語法所以它有一個專門的編譯器用來生成遵守Java位元組編碼規範的Class檔案。
主要功能:日誌記錄,效能統計,安全控制,事務處理,異常處理等等。
主要意圖:將日誌記錄,效能統計,安全控制,事務處理,異常處理等程式碼從業務邏輯程式碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的程式碼。
AOP與OOP區別
OOP(物件導向程式設計)針對業務處理過程的實體及其屬性和行為進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。
而AOP則是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。這兩種設計思想在目標上有著本質的差異。
換而言之,OOD/OOP面向名詞領域,AOP面向動詞領域。
AOP相關術語
- 目標物件target 指的是需要被增強的物件,由於spring aop是通過代理模式實現,從而這個物件永遠是被代理物件。
- 連線點(join point) 所謂連線點是指那些被攔截到的點,在spring中這些點指的是方法,因為spring只支援方法型別的連線點。
- 切入點(pointcut) 表示一組 joint point,這些 joint point 或是通過邏輯關係組合起來,或是通過通配、正規表示式等方式集中起來,它定義了相應的 Advice 將要發生的地方 簡單說切入點是指我們要對哪些連線點進行攔截的定義。
- 通知(advice) 所謂通知是指攔截到連線點之後所要做的事情就是通知,通知分為前置通知,後置通知,異常通知,最終通知,環繞通知。 Advice 定義了在 pointcut 裡面定義的程式點具體要做的操作。
- 引介introduction 引介是一種特殊的通知,在不修改類程式碼的前提下,introduction可以在執行期為類動態地新增一些方法或屬性。
- 切面aspect 是切入點和通知的結合。
- 織入weaving 織入是一個過程,是將切面應用到目標物件從而建立出AOP代理物件的過程,織入可以在編譯期,類裝載期,執行期進行。 Spring採用動態織入,而aspectj採用靜態織入。
- 代理Proxy 一個類被AOP織入增強後,就產生一個結果代理類。
AOP底層實現
AOP分為靜態AOP和動態AOP。靜態AOP是指AspectJ實現的AOP,他是將切面程式碼直接編譯到Java類檔案中。動態AOP是指將切面程式碼進行動態織入實現的AOP。 Spring的AOP為動態AOP,實現的技術為: JDK提供的動態代理技術 和 CGLIB(動態位元組碼增強技術) 。
JDK動態代理
在執行 ,在JVM內部動態生成class位元組碼物件(Class物件)。Jdk動態代理只針對於介面操作
。
CGLIB動態代理
CGLIB(Code Generation Library)是一個開源專案,是一個強大的,高效能,高質量的Code生成類庫,它可以在執行期擴充套件Java類與實現Java介面。CGLIB包的底層是通過使用一個小而快的位元組碼處理框架ASM,來轉換位元組碼並生成新的類。
如果你要單獨使用CGLIB,那麼需要匯入cglib的jar包還需要一個asm相關jar包,但是spring框架的spring-core.jar包中已經整合了cglib與asm。
注意:jdk的動態代理只可以為介面去完成操作,而cglib它可以為沒有實現介面的類去做代理,也可以為實現介面的類去做代理。
問題:spring採用的是哪一種動態機制 如果目標物件,有介面,優先使用jdk動態代理。 如果目標物件,無介面,使用cglib動態代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//創始一個使用jdk的proxy完成動態代理工具
public class JDKProxyFactory implements InvocationHandler {
private Object target;
public JDKProxyFactory(Object target) {
this.target = target;
}
// 建立代理物件
public Object createProxy() {
// 使用Proxy完成代理物件建立
// 1.得到目標物件的ClassLoader
ClassLoader loader = target.getClass().getClassLoader();
// 2.得到目標物件的實現介面的Class[]
Class[] interfaces = target.getClass().getInterfaces();
// 3.第三個引數需要一個實現了InvocationHandler介面的物件
return Proxy.newProxyInstance(loader, interfaces, this);
}
// 在代理例項上處理方法呼叫並返回結果。
// 引數1 就是代理物件,一般不使用
// 引數2它呼叫的方法的Method物件
// 引數3呼叫的方法的引數
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 功能增強操作
System.out.println("日誌操作....");
return method.invoke(target, args);
}
}
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CglibProxyFactory implements MethodInterceptor {
// 得到目標物件
private Object target;
// 使用構造方法傳遞目標物件
public CglibProxyFactory(Object target) {
this.target = target;
}
// 建立代理物件
public Object createProxy() {
// 1.建立Enhancer
Enhancer enhance = new Enhancer();
// 2.傳遞目標物件的Class
enhance.setSuperclass(target.getClass());
// 3.設定回撥操作 (相當於InvocationHandler)
enhance.setCallback(this);
return enhance.create();
}
// 相當於InvocationHandler中的invoke
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methdoProxy) throws Throwable {
System.out.println("日誌操作....");
return method.invoke(target, args); // 與jdk的proxy中操作類似
// return methdoProxy.invokeSuper(proxy, args);
}
}
public class ProxyTest {
// 測試jdk的動態代理
@Test
public void test1() {
// 1.建立目標物件
IUserService userService = new UserServiceImpl();
// 2.通過JKDProxyFactory完成代理物件建立
JDKProxyFactory factory = new JDKProxyFactory(userService);
IUserService userServiceProxy = (IUserService) factory.createProxy();
userServiceProxy.login("tom","123");
}
//測試cglib動態代理
@Test
public void test2(){
//建立目標物件
IUserService userService = new UserServiceImpl();
//2.使用CglibProxyFactory建立代理物件
CglibProxyFactory factory=new CglibProxyFactory(userService);
UserServiceImpl userServiceProxy = (UserServiceImpl) factory.createProxy();
userServiceProxy.regist();
}
}
複製程式碼
Spring AOP程式設計
Spring的傳統aop程式設計
在傳統的spring aop開發中它支援增強(advice)有五種:
- 前置通知 目標方法執行前增強 org.springframework.aop.MethodBeforeAdvice . 後置通知 目標方法執行後增強 org.springframework.aop.AfterReturningAdvice . 環繞通知 目標方法執行前後進行增強 org.aopalliance.intercept.MethodInterceptor . 異常丟擲通知 目標方法丟擲異常後的增強 org.springframework.aop.ThrowsAdvice . 引介通知 在目標類中新增一些新的方法或屬性(不講解)org.springframework.aop.IntroductionInterceptor
經典的基於代理的AOP開發(瞭解)
基本的jar包:1. bean2. core3. context4. expression5. aop6. com.springsource.org.aopalliance
程式碼示例
public class OrderServiceImpl implements IOrderService {
@Override
public void addOrder() {
System.out.println("orderService add...");
}
@Override
public void updateOrder() {
System.out.println("orderService update...");
}
}
public class OrderHelper implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知...");
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("後置通知...");
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("環繞前....");
Object value = mi.proceed();
System.out.println("環繞後....");
return value;
}
}
<!-- 目標target -->
<bean id="orderService" class="com.lbb.aop.OrderServiceImpl"></bean>
<!-- 通知advice -->
<bean id="orderServiceAdvice" class="com.lbb.aop.OrderHelper"></bean>
<!-- 定義切點 pointcut -->
<!-- <bean id="orderServicePointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>add</value>
<value>update</value>
</list>
</property>
</bean> -->
<bean id="orderServicePointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*Order"></property>
</bean>
<!-- 切面aspect=pointcut+advice -->
<bean id="orderServiceAspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="orderServiceAdvice"/>
<property name="pointcut" ref="orderServicePointCut"/>
</bean>
<!-- 代理 proxy -->
<bean id="orderServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="orderService"/>
<property name="interceptorNames" value="orderServiceAspect"/>
<property name="proxyInterfaces" value="com.lbb.aop.IOrderService"/>
</bean>
<!-- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean> -->
<!--對於基於代理的AOP的簡化記的註釋掉這個 @Qualifier("orderServiceProxy") -->
複製程式碼
基於aspectJ切點傳統開發
注意1:需要在xml配置檔案中匯入aop宣告
注意2:因為我們使用aspectj的切面宣告方式 需要在匯入aspectj的jar包com.springsource.org.aspectj.weaver
<!-- 目標target -->
<bean id="orderService" class="cn.lbb.aop.OrderServiceImpl"></bean>
<!-- 通知advice -->
<bean id="orderServiceAdvice" class="cn.lbb.aop.OrderHelper"></bean>
<!-- 使用aop標籤來完成切面與切點宣告 -->
<aop:config>
<!-- 定義切點 -->
<aop:pointcut expression="execution(* cn.lbb.aop.IOrderService.*(..))"
id="orderServicePointCut" />
<!-- 定義切面 -->
<aop:advisor advice-ref="orderServiceAdvice" pointcut-ref="orderServicePointCut" />
<!-- <aop:aspect></aop:aspect> aspectj框架它定義切面使用的 -->
</aop:config>
複製程式碼
在配置檔案中配置切面(切面=切點+通知)
<aop:config>
來宣告要對aop進行配置
<aop:pointcut>
它是用於宣告切點(簡單說就是對哪些方法進行攔截)
<aop:advisor>
定義傳統的aop的切面,傳統的aop切面它只能包含一個切點與一個增強
<aop:aspect>
定義aspectj框架的切面.,它可以包含多個切點與多個通知
切點表示式寫法
這個語法源於aspectJ的語法,spring中aop開發,對aspectj不是完全支援,只支援部分語法。
在開發中使用的比較多的是execution語法。
關於execution語法常用
1. execution(public * *()) 所有的public的方法 2. execution(* cn.itheima.aop.*.*(..)) 所有的aop包下的所有類的方法(不包含子包) . execution(* cn.itheima.aop..*.*(..)) 所有的aop包及其子包下的所有類的方法 . execution(* cn.itheima.aop.IOrderService.*(..)) IOrderService介面中定義的所有方法 . execution(* cn.itheima.aop.IOrderService+.*(..)) 匹配實現特定介面所有類的方法 . execution(* save*(..)) 區配所有的以save開頭的方法
Spring整合aspectj框架實現的aop
在現在的開發中使用這種方案比較多. 在spring2.0以後它支援jdk1.5註解,而整合aspectj後可以使用aspectj語法,可以簡化開發。
Aspect:切面 =切點+通知(多個切點與多個通知的組合) AspectJ 它是一個第三方框架,spring從2.0後可以使用aspectJ框架的部分語法.
AspectJ框架它定義的通知型別有6種
- 前置通知Before 相當於BeforeAdvice . 後置通知AfterReturning 相當於AfterReturningAdvice . 環繞通知 Around 相當於MethodInterceptor . 丟擲通知AfterThrowing 相當於ThrowAdvice . 引介通知DeclareParents 相當於IntroductionInterceptor . 最終通知After 不管是否異常,該通知都會執行 相比spring 的傳統AOP Advice多了一個最終通知
基於xml配置宣告式事務管理方案
開發步驟
第一步:建立目標(target)
第二步:建立通知(增強 advice) 注意:在aspectj中它的增強可以不實現任何介面,只需要定義出增強功能(方法)
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
//advice 通知
public class UserServiceHelper {
// 前置通知
public void before(JoinPoint jp) {
System.out.println("攔截的目標類:" + jp.getSignature().getDeclaringTypeName());
System.out.println("攔截的方法名稱:" + jp.getSignature().getName());
System.out.println("前置通知");
}
// 前置通知
public void before1() {
System.out.println("前置通知");
}
// 後置通知
public void afterReturning(JoinPoint jp, Object val) {
System.out.println("目標方法返回值:" + val);
System.out.println("後置通知");
}
// 環繞通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("環繞前....");
Object value = pjp.proceed(); // 執行目標行為
System.out.println("環繞後....");
return value;
}
// 異常丟擲通知
public void afterThrowing(JoinPoint jp,Throwable ex) {
System.out.println("發現了異常。。。。"+ex);
}
// 最終通知
public void after(JoinPoint jp) {
System.out.println(jp.getSignature().getName());
System.out.println("最終通知");
}
}
複製程式碼
第三步:在spring的xml 配置檔案中來配置
<aop:config>下的<aop:aspect>是aspectJ框架用來宣告切面的。
<!-- target -->
<bean id="userService" class="com.lbb.aspectj.UserServiceImpl"/>
<!-- advice-->
<bean id="userServiceAdvice" class="com.lbb.aspectj.UserServiceHelper"/>
<!-- 使用aop:config來宣告 使用aop:aspect來配置切面 -->
<aop:config proxy-target-class="true">
<aop:aspect ref="userServiceAdvice">
<aop:pointcut expression="execution(* *.del(..))" id="delPointCut"/>
<aop:before method="before" pointcut-ref="delPointCut"/>
<aop:before method="before1" pointcut-ref="delPointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="delPointCut" returning="val"/>
<aop:around method="around" pointcut-ref="delPointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="delPointCut" throwing="ex"/>
<aop:after method="after" pointcut-ref="delPointCut"/>
</aop:aspect>
</aop:config>
複製程式碼
注意:異常丟擲:目標行為只有丟擲了異常後才會執行這個增強方法。最終通知:無論是否有異常,最終通知都會執行。
Proxy-target-class的值預設是false,它代表有介面使用proxy代理。如果現在對目標要使用cglib代理(不考慮是否有介面),只需要將proxy-target-class設定為true。
基於annotation方案
在spring的配置檔案中配置掃描註解
第一步:編寫目標
在spring的配置檔案中配置掃描註解<context:component-scan base-package="cn.lbb" />
第二步:編寫增強(advice)
使用@Aspect來宣告切面
使用@Before來宣告前置通知
注意:必須在spring的配置檔案中開啟aspectJ註解自動代理功能。<aop:aspectj-autoproxy proxy-target-class="true"/>
Proxy-target-class預設值是false,代表的是如果目標是有介面的使用proxy代理,如果沒有介面使用cglib。proxy-target-class=true,不管目標是否有介面,都會使用cglib進行代理。
<context:component-scan base-package="cn.lbb" />
<!-- 開啟aspectj註解自動代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//通知
@Component
@Aspect // 宣告當前的bean就是一個切面
public class CustomerServiceHelper {
@Pointcut("execution(* *.s*(..))")
private void mypointcut(){}
@Pointcut("execution(* *.update(..))")
private void mypointcut1(){}
// 前置通知
@Before("mypointcut()||mypointcut1()")
public void before() {
System.out.println("前置通知...");
}
// 後置通知
@AfterReturning(value = "execution(* *.update(..))", returning = "value")
public void afterReturning(JoinPoint jp, Object value) {
System.out.println("後置通知,目標方法的返回是" + value);
}
// 環繞通知
@Around("mypointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("環繞前...");
Object value = pjp.proceed();
System.out.println("環繞後");
return value;
}
// 異常丟擲通知
@AfterThrowing(value = "mypointcut()", throwing = "ex")
public void afterThrowing(JoinPoint jp, Throwable ex) {
System.out.println("異常丟擲通知:" + ex);
}
// 最終通知
@After("mypointcut()")
public void after() {
System.out.println("最終通知");
}
}
複製程式碼
Spring jdbc Template
Spring提供了一個jdbc模板,它類似於dbutils工具。
匯入:jdbc與tx的相關包
快速入門
@Test
public void test1(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3366/springtest");
dataSource.setUsername("root");
dataSource.setPassword("123");
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
jdbcTemplate.execute("update t_user set name = '張三' where id=1");
}
複製程式碼
配置spring內建的連線池DriverManagerDataSource或者C3P0開源連線池配置
<!--
<bean name="driverManagerDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3366/springtest"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</bean>
-->
<context:property-placeholder location="classpath:db.properties"/>
<bean name="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="c3p0DataSource"/>
</bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class Test2 {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void test1(){
jdbcTemplate.execute("update t_user set name = '李四' where id=2");
}
@Test
public void test2(){
jdbcTemplate.update("update t_user set name = ? where id=?","tom",1);
}
@Test
public void test3(){
jdbcTemplate.update("insert into t_user values(null,?,?,?)","趙六",30,"男");
}
@Test
public void test4(){
jdbcTemplate.update("delete from t_user where id = ?",4);
}
@Test
public void test5(){
String name = jdbcTemplate.queryForObject("select name from t_user where id = ?",String.class,1);
System.out.println(name);
}
@Test
public void test6(){
Integer count = jdbcTemplate.queryForObject("select count(*) from t_user",Integer.class);
System.out.println(count);
}
@Test
public void test7(){
User user = jdbcTemplate.queryForObject("select * from t_user where id = ?",new RowMapper<User>(){
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setId(rs.getInt("age"));
user.setName(rs.getString("name"));
user.setSex(rs.getString("sex"));
return user;
}
},1);
System.out.println(user);
}
@Test
public void test8(){
List<User> user = jdbcTemplate.query("select * from t_user",new RowMapper<User>(){
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setId(rs.getInt("age"));
user.setName(rs.getString("name"));
user.setSex(rs.getString("sex"));
return user;
}
});
System.out.println(user);
}
@Test
public void test9(){
User user = jdbcTemplate.queryForObject("select * from t_user where id = ?",new BeanPropertyRowMapper<User>(User.class),1);
System.out.println(user);
List<User> users = jdbcTemplate.query("select * from t_user",new BeanPropertyRowMapper<User>(User.class));
System.out.println(users);
}
}
複製程式碼
Spring 事務管理
轉賬案例 我們讓dao去extends JdbcDaoSupport類,這個類中它建立了JdbcTempate,前提是我們需要注入一個dataSource。
<bean id="accountService" class="com.lbb.service.AccountServiceImpl">
<property name="userDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.lbb.dao.AccountDaoImpl">
<property name="dataSource" ref="c3p0DataSource"></property>
</bean>
複製程式碼
// 轉出
@Override
public void accountOut(String outname, double money) {
this.getJdbcTemplate().update("update account set money=money-? where name=?", money, outname);
}
複製程式碼
Spring事務管理機制
Spring事務管理的四個優點
- 提供一致的對於不同的事務管理的API . 支援宣告式事務管理(重點) . 程式設計事務管理(在開發中應用比較少) . 優秀的整合與Spring的資料訪問
我們重點講解spring的事務管理的相關的API,還有宣告式事務管理 Spring事務管理主要提供了三個介面來完成
- org.springframework.transaction.PlatformTransactionManager 這是一個事務管理器,可以來選擇相關的平臺(jdbc hibernate jpa…) . TransactionDefinition 它定義事務的一些相關資訊 例如 隔離 傳播 超時 只讀 . TransactionStatus 它主要描述事務具體的執行狀態
PlatformTransactionManager:平臺事務管理器
在不同的持久化層解決技術它的事務程式碼不一樣。
**JDBC開發**
Connection con=……;
con.setAutoCommit(false);//開啟事務
con.rollback();
con.commit();
**Hibernate開發**
Session session=….;
Transaction t=session.beginTransaction();
t.commit();
t.rollback();
複製程式碼
它的子類
- DataSourceTransactionManager 主要針對於JdbcTemplate開發 MyBatis開發
- HibernateTransactionManasger主要針對於Hibernate開發
- JpaTransactionManager 主要針對於JPA開發。
TransactionDefinition:它描述的是事務的定義資訊
在TransactionDefinition中定義了大量的常量
隔離
事務的四個特性 ACID 原子性 一致性 隔離性 永續性。
不考慮事務隔離性有什麼問題? 髒讀,不可重複讀 虛讀。
ISOLATION_DEFUALT 它使用後端資料庫的預設隔離級別(spring中選項) ISOLATION_READ_UNCOMMITTED 不能解決問題,會發生髒讀 不可重複讀 虛讀 ISOLATION_READ_COMMITTED 可以解決髒讀 會產生不可重複讀與虛讀。 ISOLATION_REPEATABLE_READ 可以解決髒讀,不可重複讀 解決不了虛讀 ISOLATION_SERIALIZABLE 序列化,可以解決所有問題
對於不現的資料庫,它的底層預設事務隔離級別不一樣。 Oracle資料庫它預設的是read_committed Mysql資料庫它預設的是repeatable_read.
超時:預設值是-1 它使用的是資料庫預設的超時時間。
只讀:它的值有兩個true/false,如果選擇true一般是在select操作時
它解決的是兩個被事務管理的方法互相呼叫問題。它與資料庫沒關係,是程式內部維護的問題。
以下定義了事務的傳播行為 以上操作中最常用的三種 PROPAGATION_REQUIRED 預設值 兩個操作處於同一個事務,如果之前沒有事務,新建一個事務 PROPAGATION_REQUIRES_NEW 兩個操作處於不同的事務 PROPAGATION_NESTED 它是一種巢狀事務,它是使用SavePoint來實現的。事務回滾時可以回滾到指定的savepoint,注意:它只對DataSourceTransactionManager有作用
以下了解 PROPAGATION_SUPPORTS 支援當前事務,如果不存在,就不使用事務 PROPAGATION_MANDATORY 支援當前事務,如果不存在,丟擲異常 PROPAGATION_NOT_SUPPORTED 以非事務執行,如果有事務存在,掛起當前事務 PROPAGATION_NEVER 以非事務執行,如果有事務存在,丟擲異常
TransactionStatus
它定義了事務狀態資訊,在事務執行過程中,得到某個時間點的狀態。
宣告式事務管理
- 編碼方案 不建議使用,它具有侵入性。在原有的業務程式碼基礎上去新增事務管理程式碼 . 宣告式事務控制,基於AOP對目標進行代理,新增around環繞通知。這種方案,它不具有侵入性,不需要修改原來的業務程式碼
基於xml配置宣告式事務管理方案
Spring為我們提供了一個TransactionInterceptor來完成增強,對於這個增強,我們可以使用spring為我們提供的一個標籤<tx:advice>
來完成操作。因為使用的是傳統的spring的advice,需要使用<aop:advisor>
<!-- 配置事務管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="c3p0DataSource"></property>
</bean>
<!-- 配置通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
name:必須的,對哪些方法進行事務控制
isolation 可選 設定事務隔離級別 預設是DEFAULT
propagation:可選 設定事務傳播 預設值 REQUIRED
timeout 可選 超時時間 預設值-1
read-only 可選 預設值是false 如果不是隻讀,它可以對insert update delete操作,如果是隻讀不可以。
rollback-for 可選 可以設定一個異常,如果產生這個異常,觸發事務回滾
no-rolback-for 可選 可以設定一個異常,如果產生這個異常,不會觸發事務回滾
-->
<tx:method name="account" />
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<aop:pointcut expression="execution(* com.lbb.service.IAccountService.account(..))" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
複製程式碼
基於annotation宣告式事務管理方案
可以使用@Transaction來在類或方法上新增宣告式事務管理。
注意:需要在applicationContext.xml檔案中使用<tx:annotation-driven transaction-manager="transactionManager"/>
相當於開啟註解事務控制。
總結