1. 程式人生 > 其它 >動力節點—2020最新Spring教程筆記(上)

動力節點—2020最新Spring教程筆記(上)

@

目錄

根據動力節點2020Spring筆記整理而成,視訊地址https://www.bilibili.com/video/BV1nz4y1d7uy/。

1 Spring 概述

Spring 根據程式碼的功能特點,使用 Ioc 降低業務物件之間耦合度。IoC 使得主業務在相互呼叫過程中,不用再自己維護關係了,即不用再自己建立要使用的物件了。而是由 Spring容器統一管理,自動“注入”,注入即賦值。 而 AOP 使得系統級服務得到了最大複用,且不用再由程式設計師手工將系統級服務“混雜”到主業務邏輯中了,而是由 Spring 容器統一完成“織入”。

官網:https://spring.io/

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(){
    }
}