動力節點—2020最新Spring教程筆記(上)
@
目錄根據動力節點2020Spring筆記整理而成,視訊地址https://www.bilibili.com/video/BV1nz4y1d7uy/。
1 Spring 概述
Spring 根據程式碼的功能特點,使用 Ioc 降低業務物件之間耦合度。IoC 使得主業務在相互呼叫過程中,不用再自己維護關係了,即不用再自己建立要使用的物件了。而是由 Spring容器統一管理,自動“注入”,注入即賦值。 而 AOP 使得系統級服務得到了最大複用,且不用再由程式設計師手工將系統級服務“混雜”到主業務邏輯中了,而是由 Spring 容器統一完成“織入”。
Spring 由 20 多個模組組成,它們可以分為資料訪問/整合(Data Access/Integration)、Web、面向切面程式設計(AOP, Aspects)、提供JVM的代理(Instrumentation)、訊息傳送(Messaging)、核心容器(Core Container)和測試(Test)。
2 IOC 控制反轉
控制反轉(IoC,Inversion of Control),是一個概念,是一種思想。指將傳統上由程式程式碼直接操控的物件呼叫權交給容器,通過容器來實現物件的裝配和管理。控制反轉就是對物件控制權的轉移,從程式程式碼本身反轉到了外部容器。通過容器實現物件的建立,屬性賦值,依賴的管理。
依賴:classA 類中含有 classB 的例項,在 classA 中呼叫 classB 的方法完成功能,即 classA 對 classB 有依賴。
依賴注入:DI(Dependency Injection),程式程式碼不做定位查詢,這些工作由容器自行完成。
依賴注入 DI 是指程式執行過程中,若需要呼叫另一個物件協助時,無須在程式碼中建立被呼叫者,而是依賴於外部容器,由外部容器建立後傳遞給程式。
Spring 框架使用依賴注入(DI)實現IoC。
Spring 容器是一個超級大工廠,負責建立、管理所有的 Java 物件,這些 Java 物件被稱為 Bean。Spring 容器管理著容器中 Bean 之間的依賴關係,Spring 使用“依賴注入”的方式來管理 Bean 之間的依賴關係。使用 IoC 實現物件之間的解耦和。
具體分為兩種,基於 XML 和基於註解的 DI。
2.1 基於XML的DI
2.1.1 注入分類
2.1.1.1 set注入
public class Student{
private String name;
private int age;
private School school;
public Student(String name, Integer age, School school) {
this.name = name;
this.age = age;
this.school = school;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setSchool(School school) {
this.school = school;
}
}
class School{
private String name;
private String address;
public void setAddress(String address) {
this.address = address;
}
public void setName(String schoolName) {
this.schoolName = schoolName;
}
}
set 注入,相當於呼叫 set 的方法,使用 <property />
,分為兩種,一種是簡單型別,即基本型別和String,另一種是引用型別。基本型別和String使用value,引用型別是ref。
<bean id="student" class="com.bjpowernode.ba01.Student" autowire="byName">
<property name="name" value="我" />
<property name="age" value="20" />
<property name="school" ref="school"/>
</bean>
<bean id="school" class="com.bjpowernode.ba01.School">
<property name="name" value="北大" />
<property name="address" value="北京" />
</bean>
2.1.1.2 構造注入(瞭解)
構造注入相當於呼叫構造器,使用 <constructor-arg />
。
<bean id="student" class="com.bjpowernode.ba01.Student">
<constructor-arg name="name" value="我" />
<constructor-arg name="age" value="20" />
<constructor-arg name="school" ref="school"/>
</bean>
<bean id="school" class="com.bjpowernode.ba01.School">
<property name="name" value="北大" />
<property name="address" value="北京" />
</bean>
2.1.2 自動注入
對於某個property是ref型別的,可以自動注入,不用自己寫,byName是按照名字注入。在這裡指的是Student類的屬性school和下面School類的id一樣。
<bean id="student" class="com.bjpowernode.ba01.Student" autowire="byName">
<property name="name" value="我" />
<property name="age" value="20"></property>
<!--自動注入,省略 <property name="school" ref="school"/>-->
</bean>
<!--id和上面省略的屬性名一致-->
<bean id="school" class="com.bjpowernode.ba01.School">
<property name="schoolName" value="北大"></property>
</bean>
byType按照型別注入,不要求注入的id和屬性名一致。
<bean id="student" class="com.bjpowernode.ba01.Student" autowire="byType">
<property name="name" value="我" />
<property name="age" value="20"></property>
<!--自動注入,省略 <property name="school" ref="school"/>-->
</bean>
<!--id和上面的屬性名不同-->
<bean id="school1234" class="com.bjpowernode.ba01.School">
<property name="schoolName" value="北大"></property>
</bean>
2.2 基於註解的DI
需要在 Spring 配置檔案中配置元件掃描器,用於在指定的基本包中掃描註解。
掌握下面幾個註解,@Component,@Repository,@Service,@Controller,@Value,@Autowired,@Qualifier,@Resource。
其中前四個都是定義Bean,@Repository是對DAO實現類使用,@Service對Service實現類使用,@Controller對Controller實現類使用。@Value用於基本型別和String。@Autowired用於自動注入,預設byType,結合@Qualifier實現byName。@Resource預設byName,找不到則byType。
具體如下,第一個例子是byName,School類的物件名稱為school1,要求自動注入的名稱也是school1.
@Component("school1")
public class School {
@Value("北大")
private String schoolName;
}
@Component("student")
public class Student {
@Value("王坤")
private String name;
@Value("20")
private Integer age;
@Resource(name="school1") //對應School的名字
//@Resource(..)可換成下面兩行
//@Autowired
//@Qualifier("school1")
private School school;
}
第二個例子是byType,按照School類去查詢,不關心名稱。
@Component("school1")
public class School {
@Value("北大")
private String schoolName;
}
@Component("student")
public class Student {
@Value("王坤")
private String name;
@Value("20")
private Integer age;
@Autowired //或者@Resource
private School school;
}
3 AOP面向切面程式設計
AOP(Aspect Orient Programming),面向切面程式設計。面向切面程式設計是從動態角度考慮程式執行過程。AOP 底層,就是採用動態代理模式實現的。採用了兩種代理:JDK 的動態代理,與 CGLIB的動態代理。
3.1 AOP術語
(1) 切面(Aspect)
切面泛指交叉業務邏輯。上例中的事務處理、日誌處理就可以理解為切面。常用的切面是通知(Advice)。實際就是對主業務邏輯的一種增強。
(2) 連線點(JoinPoint)
連線點指可以被切面織入的具體方法。通常業務介面中的方法均為連線點。
(3) 切入點(Pointcut)
切入點指宣告的一個或多個連線點的集合。通過切入點指定一組方法。被標記為 final 的方法是不能作為連線點與切入點的。因為最終的是不能被修改的,不能被增強的。
(4) 目標物件(Target)
目 標 對 象 指 將 要 被 增 強 的 對 象 。 即 包 含 主 業 務 邏 輯 的 類 的 對 象 。
(5) 通知(Advice)
通知表示切面的執行時間,Advice 也叫增強。通知定義了增強程式碼切入到目的碼的時間點,是目標方
法執行之前執行,還是之後執行等。通知型別不同,切入時間不同。
切入點定義切入的位置,通知定義切入的時間。
3.2 AspectJ
3.2.1 切入點表示式
下面為切入點表示式,用中文表述為
execution(訪問許可權 方法返回值 方法宣告(引數) 異常型別)
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
舉例
execution(public * *(..))
指定切入點為:任意公共方法。
execution(* set*(..))
指定切入點為:任何一個以“set”開始的方法。
execution(* com.xyz.service..(..))
指定切入點為:定義在 service 包裡的任意類的任意方法。
execution(* com.xyz.service...(..))
指定切入點為:定義在 service 包或者子包裡的任意類的任意方法。“..”出現在類名中時,後
面必須跟“*”,表示包、子包下的所有類。
execution(* ..service..*(..))
指定所有包下的 serivce 子包下所有類(介面)中所有方法為切入點
3.2.2 5種通知型別
所有通知方法可以包含一個 JoinPoint 型別引數。該型別的物件本身就是切入點表示式。通過該引數,可獲取切入點表示式、方法簽名、目標物件等。
AspectJ 中常用的通知有五種型別:
(1)前置通知 @Before
(2)後置通知 @AfterReturing
該註解的 returning 屬性就是用於指定接收方法返回值的變數名的,可作為通知的引數
(3)環繞通知 @Around
在目標方法執行之前之後執行。被註解為環繞增強的方法要有返回值,Object 型別。並且方法可以包含一個 ProceedingJoinPoint 型別的引數。介面 ProceedingJoinPoint 其有一個proceed()方法,用於執行目標方法。若目標方法有返回值,則該方法的返回值就是目標方法的返回值。最後,環繞增強方法將其返回值返回。該增強方法實際是攔截了目標方法的執行。
(4)異常通知 @AfterThrowing
該註解的 throwing 屬性用於指定所發生的異常類物件,可作為通知的引數
(5)最終通知 @After
無論目標方法是否執行,該通知都會執行。
(6) 定義切入點 @PointCut
可以用一個方法名錶示一個切入點表示式,其他通知使用的時候可以用對應的方法取代切入點表示式。
注:在5種通知都有的情況下,如果目標物件的方法本身有異常,執行順序是Around->Before->方法->Around->After->AfterThrowing,如果沒有異常,執行順序是Around->Before->方法->Around->After->AfterReturing。如果其他的通知出現異常,AfterThrowing同樣會執行。特別的,如果 AfterReturing 丟擲異常,則5種通知都會執行。
示例如下:
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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- <context:component-scan base-package="com.bjpowernode.service" />-->
<bean id="someServiceImpl" class="com.bjpowernode.service.SomeServiceImpl"></bean>
<bean id="myAspect" class="com.bjpowernode.service.MyAspect"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
介面:
package com.bjpowernode.service;
public interface SomeService {
String doOther(int num);
}
實現類:
package com.bjpowernode.service;
public class SomeServiceImpl implements SomeService {
@Override
public String doOther(int num) {
System.out.println("業務方法"+1/num);
return String.valueOf(num);
}
}
切面類:
package com.bjpowernode.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
@Aspect
public class MyAspect {
@Before("mypt()")
public void myBefore(JoinPoint jp){
Signature signature = jp.getSignature();
System.out.println("方法定義"+signature);
for(Object arg:jp.getArgs()){
System.out.println("引數"+arg+","+arg.getClass());
}
System.out.println("Before");
}
@AfterReturning(value = "mypt()",returning = "retu")
public void myAfterReturning(JoinPoint jp,Object retu){ ;
System.out.println("返回的是"+retu);
System.out.println("afterReturning");
System.out.println(1/0);
}
@Around(value = "mypt()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object obj = null;
System.out.println("Around");
System.out.println("環繞通知,通知之前");
obj = pjp.proceed();
System.out.println("環繞通知,通知之後");
return obj;
}
@AfterThrowing(value = "mypt()",throwing="ex")
public void myAfterThrowing(Throwable ex){
System.out.println("AfterThrowing:"+ex.getMessage());
}
@After("mypt()")
public void myAfter(){
System.out.println("After");
// System.out.println(1/0);
}
@Pointcut("execution(* com.bjpowernode.service.SomeServiceImpl.do*(..))")
private void mypt(){
}
}