喝完可樂桶後程序員迴歸本源,開源Spring基礎內容
週六了,又是摸魚的一天,今天還有點不在狀態,腦瓜子迷迷糊糊的,昨晚出去喝可樂桶喝的腦子到現在都不是很正常(奉勸各位可以自己小酌:450ml威士忌+1L多一點可樂剛剛好,可能是我酒量不好),正好沒啥事就想整理一下自己的資料夾,發現了很久之前整理的一個spring基礎的思維導圖,如下:
今天,就以這份思維導圖為基礎,講解一下spring基礎的內容,好了,我們來看一下**文字和程式碼**的詳細解析吧
**需要這份思維導圖的,可以關注公眾號:Java架構師聯盟,後臺回覆Java即可**
# 什麼是Spring
spring是一個輕量級的控制反轉(ioc)和麵向切面程式設計(AOP)的容器框架。
- 輕量:從大小與開銷兩方面而言Spring都是輕量的。完整的Spring框架可以在一個大小隻有1MB多的jar檔案裡釋出;並且Spring所需的處理開銷也是微不足道的。
- 非入侵:在應用中,一般不需要引用springjar包裡的類
- 控制反轉:Spring的核心機制,通過控制反轉技術促進了鬆耦合。簡單來講,就是把物件建立的權力交給了容器,讓容器來管理物件。
- 面向切面:允許通過分離應用的業務邏輯與系統級服務進行內聚性的開發。AOP是基於代理的,通俗一點就是把 核心業務 和 系統服務(日誌系統、許可權管理等) 分開。
# Spring的核心配置和類
1. **applicationContext.xml**:核心配置檔案。作用:用於配置所有的類,這些類可以稱為springbean
2. **BeanFactory**:容器的工廠類(介面)。作用:用於建立或獲取springbean,即spring管理的物件。
3. **ApplicationContext**:應用上下文(介面)他是BeanFactory的子類 作用:用於建立或獲取springbean。功能比BeanFactory強大。**BeanFactory**和**ApplicationContext**的區別: *BeanFactory*:懶載入 需要某個物件再去載入 *ApplicationContext*:非懶載入 一次性載入所有的物件
# Spring IOC
**控制反轉**:把物件的建立、銷燬的權利交給容器框架,由容器來管理物件的生命週期。ioc不是新的技術,只是一種思想或理念,實現鬆耦合。
IOC包括**依賴注入**(**DI**,核心) 和 依賴查詢。
**DI**:依賴注入,簡單來講就是在spring例項化物件的時候,由容器來設定這些物件的屬性。
# spring的注入方式
# 屬性的注入(set方法注入)
**前提要有對應的setter方法**
以下為spring配置檔案程式碼 java bean忽略。
```
<bean id="person" class="com.xx.Person">
<property name = "name" value = "xzy"/>
<property name = "coll">
<list>
<value>list</value>
<value>list2</value>
</list>
</property>
<property name = "map">
<map>
<entry key = "age" value = "21" />
</map>
</property>
<property name = "arr">
<array>
<value>java</value>
<value>C++</value>
</array>
</property>
</bean>
```
# 通過構造器注入
需要構造方法,javaBean程式碼:
```
public class User {
private String name;
private Integer age;
public User(String name,Integer age){
this.name = name;
this.age = age;
}
}
```
配置檔案程式碼:
```
<!--通過構造器的方式-->
<bean id = "user" class = "cn.pojo.User">
<constructor-arg value = "Jay" ></constructor-arg>
<constructor-arg value = "21" />
</bean>
<!--指定下標的方式-->
<bean id="user" class="cn.pojo.User">
<constructor-arg value="44" index="1"/>
<constructor-arg value="Jack" index="0"/></bean>
<!--指定在構造中的引數名稱-->
<bean id = "user" class = "cn.pojo.User">
<constructor-arg value="44" name="age" />
<constructor-arg value="xxx" name = "name" />
</bean>
```
# 注入其他類
```
<!--通過構造器注入-->
<bean id = "user" class = "com.xx.User">
<constructor-arg value="Jack"></constructor-arg>
<constructor-arg value="44"></constructor-arg>
<!--引用的方式 設定引用id-->
<property name = "car" ref = "car"></property>
</bean>
<bean id = "car" class = "com.xx.Car">
<property name = "type" value = "BWM"></property>
</bean>
<!--內部宣告,用這種方式宣告,別的bean不能引用了-->
<property name = "car">
<bean class = "cn.xx.Car">
<property name = "type" value = "紅旗"/>
</bean>
</property>
```
# bean元素中的屬性
- id:Bean的唯一識別符號
- name:通過name對Bean進行管理和配置 name可以多個每個以逗號隔開。
- class:指定了Bean的具體實現類,必須是完整的類名 實用類的全限定名
- scope:設定Bean例項的作用域,其屬性有singleton(單例)、prototype(原型)、request、session、和global
Session,預設值為singleton.
- constructor-arg:的子元素,可以傳入構造引數例項化 該元素index屬性指定構造引數的序號(從0開始).
- property:的子元素,通過呼叫Bean例項中的setter方法完成屬性賦值.
- ref:property、constructor-arg等元素的子元素,該元素中的bean屬性用於指定對Bean工廠中某個Bean例項的引用;
- value:property、constructor-arg等元素的子元素,用來直接指定一個常量值;
- list:用於封裝List或陣列型別的依賴注入。
- set:用於封裝Set或陣列型別的依賴注入。
- map:用於封裝Map或陣列型別的依賴注入。
- entry:map元素的子元素 用於設定一個鍵值對。
# Bean的例項化
# 構造器例項化
Spring容器通過Bean對應的預設建構函式來例項化Bean。
# 靜態工廠方式例項化
通過建立靜態工廠的方式來建立Bean的例項
```
public class BeanFactory {
public static Bean createBean(){
return new Bean();
}
}
<!--factory-method-->
<bean id = "bean" class = "com.xx.BeanFactory" factory-method = "createBean"></bean>
```
# 例項工廠化方式例項化
不再使用靜態方法建立Bean例項,而是採用直接建立Bean例項的方式.
```
public class BeanFactory {
public BeanFactory(){
System.err.println("BeanFactory 工廠例項化")
}
public static Bean createBean(){
return new Bean();
}
}
<!--配置工廠-->
<bean id = "beanFactory" class = "com.xx.BeanFactory" /><!--factory-bean 屬性指向配置的例項工廠;factory-method屬性確定使用工廠的哪個方法-->
<bean id = "bean" factory-bean="beanFactory" factory-method="createBean"/>
```
# Bean的作用域
# singleton:單例模式
Spring IOC容器中只會存在**一個共享的Bean例項**,無論有多少個Bean引用它,始終指向同一物件。
```
配置檔案:
<bean id ="dog" class = "com.bean.Dog" scope="singleton"></bean>
java程式碼:
public void test(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Dog dog1 = (Dog) ctx.getBean("dog");
System.err.println(dog1);
Dog dog2 = (Dog) ctx.getBean("dog");
System.err.println(dog2);
}
// 輸出的結果一致,表明為單例模式。
```
# prototype:原型模式
每次通過Spring容器獲取prototype定義的bean時,容器都將建立一個**新的Bean例項**,每個Bean例項都有自己的**屬性和狀態**。
# request
在一次Http請求中,容器會返回該Bean的**同一例項**。而對不同的Http請求則會產生新的Bean,而且該bean**僅在當前HttpRequest內有效**。
針對每一次Http請求,Spring容器根據該bean的定義建立一個全新的例項,且該例項僅在當前Http請求內有效,而其它請求**無法看到當前請求中狀態的變化**,噹噹前Http請求結束,該bean例項也將會被銷燬。
# session
在一次Http Session中,容器會返回該Bean的同一例項。而對不同的Session請求則會建立新的例項,該bean例項**僅在當前Session內有效**。
同Http請求相同,每一次session請求建立新的例項,而不同的例項之間不共享屬性,且例項**僅在自己的session請求內有效**,請求結束,則例項將被銷燬。
# Bean的裝配方式
# 基於XML的裝配
兩種裝配方式:setter注入和構造器注入。
設定注入的兩個要求:
- Bean類必須提供一個預設的午餐構造方法
- Bean類必須為需要注入的屬性提供對應的setter方法
# 基於註解(annotation)
常見註解:
- @Component 是所有受Spring管理元件的通用形式。
- @Repository 持久層使用,dao層使用。
- @Service 業務類,表示業務層元件,Service層使用。
- @Controller 控制器,表示web層元件
- @Autowired 按照型別來裝配
- @Resource 根據名字去裝配
# 自動裝配
屬性值說明語法default 預設值由的default-autowire屬性值確定default-autowire=“default”byName根據屬性名稱自動裝配byType根據屬性的資料型別自動裝配constructor根據建構函式引數的資料型別自動裝配no不適用自動裝配,必須通過ref元素定義
# Spring AOP
實現**核心業務**和**系統服務**程式碼之間的分開 通過一種特殊的技術手段來實現核心業務執行時能夠實現系統服務的功能;
***aop的本質是代理*** 通過對方法進行攔截、增強來實現的。
# AOP的基本概念
採用**橫向抽取機制**,把分散在各個方法中的**相同的程式碼抽取出來**,然後在編譯器或者是執行時再把這些程式碼應用到所需要執行的地方。
**通知(Advice)**:aop在切點上執行的增強處理。
通知的型別:
- 前通知(methodBeforeAdvice):方法執行前做增強
- 後通知(methodAfterAdvice):方法執行後做增強
- 環繞通知(MethodInterceptor):方法執行前和後做增強
- 返回通知(AfterReturningAdvice): 成功返回後 進行增強
- 異常通知(ThrowsAdvice): 丟擲異常後 進行通知
**切點(Pointcut)**:就是帶有通知的連線點,就是對那些類 哪些方法做增強。
切點的型別:
基於正則表示式 JdkRegexpMethodPointcut
基於AspectJ的表示式 AspectJExpressionPointcut
**切面(Aspect)**:通常上就是一個類,裡面定義了 **通知** 和 **切點**
***AOP = 通知 + 切點***
# AOP案例 java實現
```
// com.bean.User
public class User {
public String login(String name){
return "name is "+ name ;
}
public String login(String name , int pwd){
return "name is "+name+", pwd is "+pwd ;
}
}
// 新建一個環繞通知
public class MyAroundAdivice implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation arg0) throws Throwable{
System.err.println("方法執行前:"+arg0.getMethod().getName()+","+arg0.getArguments()[0]);
Object obj = arg0.proceed();
System.err.println("執行完成後...");
return obj;
}
}
// 新建基於AspectJ切點的測試類
@Test
public void test(){
// 1. 宣告通知
Advice advice = new MyAroundAdivice();
//2、基於AspectJ宣告切點物件
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
//3、設定表示式
/*返回型別為String型別 com.bean.User的login方法 引數為String型別*/
//cut.setExpression("execution (String com.bean.User.login(String))");
//任意放回型別 com包下包括com子包下 任意方法 任意的引數0~n
cut.setExpression("execution(* com..*(..))");
//4、切點+通知 =AOP
Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
//5、宣告代理bean
ProxyFactory proxy = new ProxyFactory(new User());
//6、設定aop
proxy.addAdvisor(advisor);
//7、從代理中獲取代理的物件
User user = (User) proxy.getProxy();
user.login("rose");
System.err.println("---------------");
user.login("jack",123);
}
```
AspectJ語法
*這個目錄下的,或是類上的所有…任意的多個。0~N個execution (* com.bean.User.login(String,int))對login方法,必須要接收兩個引數,且引數的型別必須是Strig,int的,且返回值無限制。且這個方法必須是User這個類的Execution (* com.*.*(…))返回所有的型別 所有在com包下的類,不包含子包 類的所有方法 接收任意多個引數 0~NExecution (* com…*.*(…))在com包下的,包含子包下的所有類的所有方法,所有引數都切入execution(* com…*.*(String)) || execution(* com…*.*(*,int))|| 邏輯或
# AOP案例 基於XML宣告式AspectJ
```
<bean id = "user" class = "com.bean.User">
<!-- 定義一個切面 -->
<bean id="myBeforeAdvice" class="com.demo03.MyAdvice"></bean>
<aop:config>
<!-- 配置切入點 -->
<!-- 表示式(用來表示方法) -->
<!-- execution(<訪問修飾符>?<返回型別><方法名>(<引數>)<異常>),返回值,方法名,引數不能少 -->
<!-- *代表:任意值 方法名:全類名.方法名 引數中的..:任意個數,任意型別 -->
<aop:pointcut expression="execution(* com..*(..))" id="myPointcut" />
<!-- 切面配置 -->
<aop:aspect ref="myBeforeAdvice">
<!-- 配置前通知 -->
<aop:before method="doBefore" pointcut-ref="myPointcut" />
<!-- 配置後通知 -->
<aop:after method="doAfter" pointcut-ref="myPointcut" />
<!-- 配置返回通知 -->
<aop:after-returning method="doReturnAfter"
pointcut-ref="myPointcut" />
<!-- 配置環繞通知 -->
<aop:around method="doAround" pointcut-ref="myPointcut" />
<!-- 異常通知 -->
<aop:after-throwing method="doThrowing"
pointcut-ref="myPointcut" throwing="e" />
</aop:aspect>
</aop:config>
</bean>
```
# 基於註解的宣告式AspectJ (常用)
註解功能@Aspect註解在類上,宣告這是一個切面@Pointcut註解在方法上宣告是一個切點,且要宣告aspectj的切點表示式@Before前通知@After @AfterRetuning –正確執行成功返回值 @AfterThrow – 錯誤的執行,丟擲異常後通知@Around環繞通知
第一步:新建註解的切面類
```
@Aspect
@Component
public class MyAspect {
//定義一個切入點表示式 使用一個返回值為void,方法體為空的方法來命名切點
@Pointcut("execution(* com..*(..))")
public void myPointCut(){}
//前置通知
@Before("myPointCut()")
public void doBefore(JoinPoint joinPoint)
{
System.out.println("前置通知,方法開始..."+":目標類是"+joinPoint.getTarget()+",被植入的增強方法是:"+joinPoint.getSignature().getName());
}
//後置通知
@After("myPointCut()")
public void doAfter()
{
System.out.println("後置通知,方法結束...");
}
//返回通知
@AfterReturning("myPointCut()")
public void doReturnAfter()
{
System.out.println("返回通知,方法最終結束...");
}
/**
* 環繞通知 ProceedingJoinPoint用來呼叫被增強的方法
* 必須是Object的返回型別
* 必須接受一個引數,型別為ProceedingJoinPoint
* 必須throws Throwable
*/
@Around("myPointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable
{
System.out.println("環繞通知:begin...");
//執行被增強的方法
Object obj = joinPoint.proceed();
System.out.println("環繞通知:end.....");
return obj;
}
@AfterThrowing(value="myPointCut()",throwing="e")
public void doThrowing(Throwable e){
System.out.println("異常通知......."+e.getMessage());
}
}
```
第二步:xml配置檔案
```
<bean id="user" class="com.bean.User"></bean>
<context:component-scan base-package="com.xxx"></context:component-scan>
<!-- 啟動基於註解的宣告式AspectJ支援 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
```
第三步:測試方法
```
@Test
public void test1() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/xxx/applicationContext.xml");
User user = ctx.getBean("user",User.class);
user.say();
//user.run();
}
```
> 如有問題 請