spring的一些筆記
核心容器的兩個接口引發的問題
-
ApplicationContext
單例對象適用
它在構建核心容器時,創建對象是使用的是立即加載的方式,只要一讀取完配置文件就立即創建對象
-
BeanFactory
多例對象適用
它在構建核心容器時,創建對象是使用的是延時加載的方式,什麼時候要使用了,才真正獲取對象
spring創建bean的三種方式
-
使用默認構造方法創建
在spring的配置文件中使用bean標籤,配以id與class屬性,且沒有其它屬性和標籤時,
採用的就是默認構造函數創建bean對象,此時如果類中沒有構造函數,則無法創建
<bean id="userdaoService" class="com.Cracker.service.Impl.UserdaoService"></bean>
-
第二種:使用普通工廠中的方法創建對象(使用某個類中的方法創建對象,並存入spring容器)
<bean id="instanceFactory" class="com.Cracker.factory.InstanceFactory"></bean> <bean id="userdaoService" factory-bean="instanceFactory" factory-method="getUserdaoService"></bean>
-
第三種方式:使用工廠中的靜態方法創建對象(使用某個類中的靜態方法創建對象,並存入spring容器)
<bean id="userdaoService" class="com.Cracker.factory.StaticFactory" factory-method="getUserdaoService"></bean>
-
bean的作用範圍:
-
bean標籤的scope屬性:
-
作用:用於指定bean的作用範圍
-
取值:常用的就是單例的和多例的
singleton:單例的(默認值)
prototype:多例的
request:作用於web應用的請求範圍
session:作用於web應用的會話範圍
global-session:作用於集羣環境的會話範圍(全局會話範圍),當不是集羣環境時,它就是session
-
-
-
bean對象的生命週期
-
單例對象
- 出生:當容器創建時對象出生
- 活着:只要容器還在,對象一直活着
- 死亡:容器銷燬,對象消亡
- 總結:單例對象的生命週期和容器相同
-
多例對象
- 出生:當我們使用對象時spring框架爲我們創建
- 活着:對象只要是在使用過程中就一直活着。
- 死亡:當對象長時間不用,且沒有別的對象引用時,由Java的垃圾回收器回收
-
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype" init-method="init" destroy-method="destroy"></bean>
-
spring依賴注入
-
依賴注入(Dependency Injection):依賴關係的維護
-
IOC的作用:降低程序間的耦合(依賴關係)
-
依賴關係的管理:
以後都交給spring來維護,在當前類需要用到其他類的對象,由spring爲我們提供,我們只需要在配置文件中說明
-
依賴注入能注入的數據:
- 基本類型和String
- 其他bean類型(在配置文件中或者註解配置過的bean)
- 複雜類型/集合類型
-
注入的方式:
- 使用構造函數提供
- 使用set方法提供
- 使用註解提供
構造函數注入:
-
使用的標籤:constructor-arg
-
標籤出現的位置:bean標籤的內部
-
標籤中的屬性:
- type:用於指定要注入的數據的數據類型,該數據類型也是構造函數中某個或某些參數的類型
- index:用於指定要注入的數據給構造函數中指定索引位置的參數賦值。索引的位置是從0開始
- name:用於指定給構造函數中指定名稱的參數賦值
- value:用於提供基本類型和String類型的數據
- ref:用於指定其他的bean類型數據。它指的就是在spring的Ioc核心容器中出現過的bean對象
-
優勢:
在獲取bean對象時,注入數據是必須的操作,否則對象無法創建成功。
-
弊端:
改變了bean對象的實例化方式,使我們在創建對象時,如果用不到這些數據,也必須提供。
-
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="泰斯特"></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>
set方法注入
-
涉及的標籤:property
-
出現的位置:bean標籤的內部
-
標籤的屬性
- name:用於指定注入時所調用的set方法名稱(不是屬性名)
- value:用於提供基本類型和String類型的數據
- ref:用於指定其他的bean類型數據。它指的就是在spring的Ioc核心容器中出現過的bean對象
-
優勢:
創建對象時沒有明確的限制,可以直接使用默認構造函數
-
弊端:
如果有某個成員必須有值,則獲取對象是有可能set方法沒有執行。
-
<bean id="userdaoService1" class="com.Cracker.service.Impl.UserdaoService"> <property name="name" value="李四"></property> <property name="age" value="20"></property> <property name="date" ref="date"></property> </bean>
-
使用的是默認構造器,沒有,無法生成對象
複雜類型的注入/集合類型的注入
-
用於給List結構集合注入的標籤:list,array,set(當然也有單獨的標籤)
-
用於個Map結構集合注入的標籤: map ,props(當然也有單獨的標籤)
-
結構相同,標籤可以互換
<bean id="userdaoServiceOne" class="com.Cracker.service.Impl.UserdaoServiceOne"> <property name="myStrs" > <list> <value> myStrs </value> </list> </property> <property name="myList"> <list> <value> myList </value> </list> </property> <property name="myMap"> <map> <entry key="a"> <value> AAA </value> </entry> </map> </property> <property name="properties"> <props> <prop key="b"> BBB </prop> </props> </property> </bean>
使用註解
-
用於創建對象的: 他們的作用就和在XML配置文件中編寫一個
<bean>
標籤實現的功能是一樣的- Component:
- 作用:用於把當前類對象存入spring容器中
- 屬性:value:用於指定bean的id。當我們不寫時,它的默認值是當前類名,且首字母改小寫。
- Controller:一般用在表現層
- Service:一般用在業務層
- Repository:一般用在持久層
- 以上三個註解他們的作用和屬性與Component是一模一樣。他們三個是spring框架爲我們提供明確的三層使用的註解,使我們的三層對象更加清晰
- Component:
-
用於注入數據的:他們的作用就和在xml配置文件中的bean標籤中寫一個
<property>
標籤的作用是一樣的-
Autowired:
-
作用:
自動按照類型注入。只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就可以注入成功
如果ioc容器中沒有任何bean的類型和要注入的變量類型匹配,則報錯。
如果Ioc容器中有多個類型匹配時:先按照接口(IOC容器中的value的接口類型)匹配,如果有相同的,再按照id(IOC容器中的key)匹配,沒有則報錯
-
出現位置:可以是變量上,也可以是方法上
-
細節:在使用註解注入時,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
- Scope
-
和生命週期相關了解:他們的作用就和在bean標籤中使用init-method和destroy-methode的作用是一樣的
- PreDestroy:用於指定銷燬方法
- PostConstruct:用於指定初始化方法
基於xml的ioc配置掃描註解
-
告知spring要掃描註解的包
<context:component-scan base-package="com"></context:component-scan>
基於註解的ioc配置
-
Configuration註解:
- 作用:指定當前類作爲配置類
- 細節:當配置類作爲AnnotationConfigApplicationContext對象創建的參數時,該註解可以不寫。
-
ComponentScan註解:
-
作用:用於通過註解指定spring在創建容器時要掃描的包
-
屬性:
-
value:它和basePackages的作用是一樣的,都是用於指定創建容器時要掃描的包。
我們使用此註解就等同於在xml中配置了:
<context:component-scan base-package="com"></context:component-scan> >
-
-
-
Bean註解:
- 作用:用於把當前的返回值作爲bean對象當如spring的ioc容器中
- 屬性:
- name:指定bean對象id,不寫,默認爲方法名
- 細節:當我們使用註解的方法有參數時,spring會去容器中查找是否可用的bean對象,方法與Autowired註解是一樣的
-
Import註解:
- 作用:用於導入其它配置類
- 屬性:
- value:用於指定其它配置類的字節碼。當我們使用Import的註解之後,有Import註解的類就父配置類,而導入的都是子配置類
-
PropertySource註解:
- 作用:用於指定properties文件的位置
- 屬性:
- value:指定文件的名稱和路徑。關鍵字:classpath,表示類路徑下
Spring整合junit的配置:
-
導入spring整合junit的jar包:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency>
-
使用junit提供的註解
@RunWith
把junit原有的main方法,替換成spring提供的main方法:@RunWith(SpringJUnit4ClassRunner.class)
-
告知spring的運行器,spring和ioc創建是基於xml還是註解,並說明位置:
@ContextConfiguration:
- locations:指定xml文件的位置,加上classpath關鍵字,表示在類路徑下(不要在文件名前加上空格,或許會報錯)
- classes:指定註解類所在地位置
動態代理
-
特點:字節碼隨用隨創建,隨用隨加載
-
作用:不修改源碼的基礎上對方法增強
-
分類:
-
基於接口的動態代理(涉及的類:Proxy;提供者:JDK官方)
-
如何創建代理對象:使用Proxy類的newProxyInstance方法
-
創建代理對象的要求:被代理類最少實現一個接口,如果沒有則不能使用
-
newProxyInstance方法的參數
-
ClassLoader:類加載器
用來加載代理對象字節碼的。和被代理對象使用相同的類加載器。固定寫法
-
Class[]:字節碼數組
用來讓代理對象與被代理對象有相同的方法。固定寫法。
-
InvocationHandler:用於提供增強的代碼
它是讓我們如何寫代理。我們一般都是寫一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。此接口的實現類都是誰用誰寫
-
實例:
package proxy; /** * 對廠家要求的接口 */ public interface IProducer { /** * 銷售 * @param money */ public void saleProduct(float money); /** * 售後 * @param money */ public void afterService(float money); } package proxy.Impl; import proxy.IProducer; /** * 一個生產者 */ public class Producer implements IProducer { /** * 銷售 * @param money */ public void saleProduct(float money) { System.out.println("銷售產品,並拿到錢:"+money); } /** * 售後 * @param money */ public void afterService(float money) { System.out.println("提供售後服務,並拿到錢:"+money); } } package proxy; import proxy.Impl.Producer; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 模擬一個消費者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); IProducer proxyProducer = (IProducer)Proxy.newProxyInstance( producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 執行被代理對象的任何接口方法都會經過該方法 * @param proxy 代理對象的引用 * @param method 當前執行的方法 * @param args 當前執行的方法所需參數 * @return 和被代理對象方法具有相同的返回值 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增強的代碼 Object returnValue = null; //獲取方法執行的參數 Float money = (Float) args[0]; //判斷當前方法是不是銷售 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer,money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f); } }
-
-
-
基於子類的動態代理(涉及的類:Enhancer;提供者:第三方cglib庫)
-
如何創建代理對象:使用Enhancer類中的create方法
-
創建代理對象的要求:被代理類不能是最終類
-
create方法的參數
-
Class:字節碼
它是用於指定被代理對象的字節碼。
-
Callback:用於提供增強的代碼
它是讓我們如何寫代理。我們一般都是寫一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。此接口的實現類都是誰用誰寫。我們一般寫的都是該接口的子接口實現類:MethodInterceptor
-
實例:
package cglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import proxy.Impl.Producer; import java.lang.reflect.Method; /** * 模擬一個消費者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); Producer cglibProducer = (Producer)Enhancer.create( producer.getClass(), new MethodInterceptor() { /** * 執行被代理對象的任何接口方法都會經過該方法 * @param o 代理對象的引用 * @param method 當前執行的方法 * @param objects 當前執行的方法所需參數 * @param methodProxy 當前執行方法的代理對象 * @return 和被代理對象方法具有相同的返回值 * @throws Throwable */ public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //提供增強的代碼 Object returnValue = null; //獲取方法執行的參數 Float money = (Float) objects[0]; //判斷當前方法是不是銷售 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer,money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(20000f); } }
-
-
-
spring中基於xml的aop配置
-
把通知bean也交給spring來管理
-
使用aop:config標籤來表明開始aop配置
-
使用aop:aspect標籤來表明配置切面
- id屬性:唯一標識一個切面
- ref屬性:指定通知類的id
-
在aop:aspect標籤內使用對應標籤配置通知的類型
-
aop:before:表示配置前置通知
-
method屬性:用於指定通知類中哪個方法作爲前置通知
-
pointcut屬性:用於指定切入點表達式,該表達式的含義是指對業務層的哪些方法增強
-
切入表達式的寫法:
- 關鍵字:execution(表達式)
-
-
表達式:訪問修飾符 返回類型 包名.包名…類名.方法名(參數列表)
-
標準的表達式寫法:
public void com.Cracker.service.impl.UserService.saveUser()
- 訪問修飾符可以省略:
void com.Cracker.service.impl.UserService.saveUser()
- 返回值可以使用通配符,表示任意返回值
* com.Cracker.service.impl.UserService.saveUser()
- 包名可以使用通配符,表示任意包。但是有幾級包,就需要寫幾個*
* *.*.*.*.UserService.saveUser()
- 包名可以使用…表示當前包及其子包
* *…UserService.saveUser()
- 類名和方法名都可以使用*來實現通配
* *…*.*()
- 參數列表:
1.可以直接寫數據類型:
基本類型直接寫名稱:int
引用類型直接寫包名.類名的方式:java.lang.String
2.可以使用通配符表示任意類型,但是必須要有參數
3.可以使用…表示有無參數均可,有參數可以說任意類型
4.全通配寫法:
* *…*.*(…)
5.實際開發中切入點表達式的通常寫法:
切入到業務層實現類下的所有方法:
* com.Cracker.service.impl..(…)
-
aop:after-running:後置通知
-
aop:after-throwing:異常通知
-
aop:after:最終通知
-
aop:aroud:環繞通知
-
配置切入點表達式
id屬性用於指定表達式唯一標識;expression屬性用於指定表達式內容;
寫在aspect裏面只能在aspect裏生效,寫在外面,全局生效;
如果報錯,看約束,是否要求寫在aspect標籤之前。
-
-
環繞通知:
-
問題:
當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了
-
分析:
通過對比動態代理中的環繞通知代碼,發現動態代理的環繞通知有明確的切入點方法調用,而我們的代碼中沒有。
-
解決:
Spring框架爲我們提供了一個接口:ProceedingJoinPoint。該接口有一個方法proceed(),
此方法就相當於明確調用切入點方法。
該接口可以作爲環繞通知的方法參數,在程序執行時,spring框架會爲我們提供該接口的實現類供我們使用。 -
spring中的環繞通知:
它是spring框架爲我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。
-
spring中基於註解的aop配置
-
配置spring開啓註解aop的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
@Aspect:表明這是一個通知類
-
@Pointcut:配置切入表達式:
@Pointcut("execution(* com.Cracker.service.impl.*.*(..))") private void ptOne(){}
-
@Before:配置前置通知
@Before(value = "ptOne()") public void beforePrintLog(){ System.out.println("Logger類中的beforePrintLog方法開始記錄日誌了。。。"); }
-
@AfterReturning:後置通知
-
@AfterThrowing:異常通知
-
@After:最終通知
-
@Around:環繞通知
-
注意:
spring的aop註解執行順序有點問題,先執行Before,再執行After,然後再執行AfterReturning和AfterThrowing,所以想要用spring的aop的註解方式,建議用環繞通知