Spring 5| 輕量級的開源JavaEE框架

吃飽了就睡覺發表於2021-12-20

一、Spring框架的概述

1、Spring是輕量級的開源的JavaEE框架

2、Spring可以解決企業應用開發的複雜性

3、Spring有兩個核心的部分:IOC(控制反轉)和AOP(面向切面程式設計)

4、Spring特點

(1)方便解耦,簡化開發

(2)Aop程式設計支援

(3)方便程式的測試

(4)方便整合各種優秀框架

(5)方便進行事務的操作

(6)降低API的開發難度

二、Spring配置小案例

1.配置Spring的配置檔案以及載入

//1.載入配置檔案
ApplicationContext context = new ClassPathXmlApplicationContext("myBean.xml");
//2.獲取配置檔案建立的物件
User user = context.getBean("user1", User.class);
System.out.println(user);
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user1" class="com.cuit.bean.User">
        <property name="name" value="張三"></property>
        <property name="sex" value="男"></property>
    </bean>
</beans>

三、IOC

1、IOC(概念和原理)

  • IOC容器底層就是物件工廠
  • 控制反轉,即將物件的建立和物件之間的呼叫過程交給Spring容器來管理
  • 使用IOC的目的是為了解耦,降低耦合度
  • IOC的底層是xml解析、工廠模式、反射

2、IOC底層有兩種介面

BeanFactory:載入配置檔案的時候不建立物件,呼叫物件時才建立物件

ApplicationContext:載入配置檔案的時候建立好物件,可以為Bean配置lazy-init=true來讓Bean延遲例項化

3、IOC操作Bean管理

Spring建立物件、Spring注入屬性

Spring中有兩種型別的bean

普通bean:配置檔案中定義bean型別就是返回型別

工程bean:在配置檔案定義bean型別可以和返回型別不一樣

3.1、基於xml方式建立物件

建立物件時,預設使用無參構造方式建立物件

id:對於建立物件的唯一標識

class:類的全路徑(包類路徑),即返回型別

<bean id="user1" class="com.cuit.bean.User">

3.2、基於xml方式設定屬性(DI依賴注入)

DI:依賴注入,就是注入屬性

3.2.1、構造器方式注入

在建立的類中,新增上有參構造器

public class Book {
    private String bname;
    private String bautohr;

    public Book(String bname, String bautohr) {
        this.bname = bname;
        this.bautohr = bautohr;
    }

    public Book() {
    }
}

xml檔案

constructor-arg標籤用於設定構造器引數屬性值

<bean id="book" class="com.cuit.bean.Book" >
        <constructor-arg name="bautohr" value="達摩祖師"></constructor-arg>
        <constructor-arg name="bname" value="易筋經"></constructor-arg>
    </bean>

3.2.2、set方式進行注入

在建立的類中設定屬性,並且帶有set方法

package com.cuit.bean;

public class User {
    private String name;
    private int age;
    private String sex;

    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
}

xml檔案

<bean id="user" class="com.cuit.bean.User">
        <property name="name" value="張三"></property>
        <property name="age" value="22"></property>
        <property name="sex" value="男"></property>
</bean>
設定一些特殊值空值
<bean id="user" class="com.cuit.bean.User">
        <property name="name" value="張三"></property>
        <property name="age" value="22"></property>
        <!--設定空值-->
        <property name="sex">
            <null></null>
        </property>
    	
    	<property name="sex">
            <!--設定一些特殊值,比如<<南京>> idea中輸入cd後有提示-->
            <value> <![CDATA[
                <<南京>>            
            ]]></value>
        </property>
    </bean>
注入其他bean的方式
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.cuit.bean.User">
        <property name="name" value="張三"></property>
        <property name="age" value="22"></property>
        <!--外部bean注入屬性-->
        <property name="book" ref="book"></property>
        <property name="sex" value="男"></property>
    </bean>
    <bean id="book" class="com.cuit.bean.Book">
        <constructor-arg name="bautohr" value="達摩祖師"></constructor-arg>
        <constructor-arg name="bname" value="易筋經"></constructor-arg>
    </bean>
    <!--直接使用p名稱空間注入屬性-->
    <bean id="user1" class="com.cuit.bean.User" p:name="李四" p:age="21" p:sex="男">
    </bean>

    <bean name="emp" class="com.cuit.bean.Emp">
        <property name="ename" value="王五"/>
        <property name="gender" value="女"/>
        <!--內部bean-->
        <property name="dept">
            <bean id="dept" class="com.cuit.bean.Dept">
                <property name="dname" value="研發部"/>
            </bean>
        </property>
    </bean>

    <bean name="emp1" class="com.cuit.bean.Emp">
        <property name="ename" value="王五"/>
        <property name="gender" value="女"/>
        <!--級聯賦值-->
        <property name="dept" ref="dept1"></property>
        <property name="dept.dname" value="技術部"></property>
    </bean>
    <bean id="dept1" class="com.cuit.bean.Dept">
        <property name="dname" value="研發部"/>
    </bean>
</beans>
注入集合型別屬性
<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/util  http://www.springframework.org/schema/beans/spring-util.xsd">
    <util:list id="list">
        <value>易筋經</value>
        <value>九陰真經</value>
        <value>九陽神功</value>
    </util:list>

    <bean id="stu" class="com.cuit.bean.Stu">
        <property name="course">
            <!--陣列的注入方式-->>
            <array>
                <value>數學</value>
                <value>英語</value>
                <value>語文</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>list1</value>
                <value>list2</value>
                <value>list3</value>
                 <!--可以使用<ref bean="容器中的bean的id或者name,但物件必須與list申請時物件相對應的"-->
            </list>
            
        </property>
        <property name="maps">
            <map>
                <entry key="key1" value="val1"></entry>
                <entry key="key2" value="val2"></entry>
                <entry key="key2" value="val3"></entry>
                
                 <!--可以使用<ref bean="容器中的bean的id或者name,但物件必須與map申請時物件相對應的"-->
            </map>
            <!--
            <props>
                <prop key="key1">value1</prop>
            </props>
			-->
        </property>
        <property name="sets">
            <set>
                <value>cf</value>
                <value>dnf</value>
                <value>lol</value>
                 <!--可以使用<ref bean="容器中的bean的id或者name,但物件必須與set申請時物件相對應的"-->
            </set>
        </property>
    </bean>
</beans>

set:使用者屬性set的情況,無重複,有個ref標籤,通過引入bean的id,引入物件

list:類似於set但是值時可以重複的,也有個ref標籤,通過引入bean的id,引入物件

map:類死props,鍵和值不一定要用String,專用於map,有個value-ref,對應於bean的id,引入物件

3.2.3、其他注入方式

需要引入第三方依賴

(1)、p名稱空間注入,可以簡化基於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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--直接使用p名稱空間注入屬性-->
    <bean id="user" class="com.cuit.bean.User" p:name="李四" p:age="21" p:sex="男">
    </bean>
</beans>

(2)、

3.2.4、bean的作用域

在Spring裡面,可以設定建立bean例項是單例項還是多例項,預設單例項物件

通過scope標籤預設的情況預設為singleton為單例項,prototype為多例項

singleton單例項模式

無論呼叫多少次getBean()方法,只會在堆空間中生成一個物件

public void testStu(){
        ApplicationContext context = new ClassPathXmlApplicationContext("myBean2.xml");
    //User user = (User)context.getBean("user");
        /*知道輸出型別之後就不需要每一次get之後都去強轉型別了*/
        Stu stu1 = context.getBean("stu", Stu.class);
        Stu stu2 = context.getBean("stu", Stu.class);
        System.out.println(stu1);
        System.out.println(stu2);
        System.out.println(stu1 == stu2);
    }
<util:list id="list">
        <value type="java.lang.String">易筋經</value>
        <value type="java.lang.String">九陰真經</value>
        <value type="java.lang.String">九陽神功</value>
    </util:list>
    <bean id="stu" class="com.cuit.bean.Stu" scope="singleton">
         <property name="lists" ref="list"/>
    </bean>

輸出

com.cuit.bean.Stu@38467116
com.cuit.bean.Stu@38467116
true

單例項模式特點

1、把bean放在IOC容器中統一進行管理,只在初始化載入的時候例項化一次,一方面提高了效率,另一方面大大降低了記憶體開銷(針對建立物件的次數)

2、不同的請求可以同時拿這個例項來用,執行例項中不同的方法時,可以同時進行,但是不可以同時執行該例項的同一個方法。其實就是單例模式的執行緒安全問題,

3、例項裡面不可以有成員變數,因為成員變數共享的話,一個對它修改,另一個再拿來用,用的是修改之後的,會不安全。

4、只有一個共享的例項存在,所有對這個bean的請求都會返回這個唯一的例項。

prototype(多例項模式)
<bean id="stu" class="com.cuit.bean.Stu" scope="prototype">
         <property name="lists" ref="list"/>
    </bean>

輸出

com.cuit.bean.Stu@5b7a7f33
com.cuit.bean.Stu@790da477
false
request、ssession、application這些只會在web開發中使用

request作用域
對應一個http請求和生命週期,當http請求呼叫作用域為request的bean的時候,Spring便會建立一個新的bean,在請求處理完成之後便及時銷燬這個bean。
session作用域
Session中所有http請求共享同一個請求的bean例項。Session結束後就銷燬bean。
globalSession作用域
與session大體相同,但僅在portlet應用中使用。

3.2.5、bean生命週期

(一)、不加後置處理器

一共五步

第一步、通過無參構造器建立bean的例項物件

第二步、對bean的set()方法進行屬性賦值以及對其他bean引用

第三步、呼叫bean的初始化方法(通過init-method標籤配置)

第四步、bean可以使用了

第五步、當容器關閉時,呼叫bean的銷燬方法(通過destroy-method標籤配置)

(二)、加上後置處理器

需要建立一個類實現BeanPostProcessor介面,並且需要在xml檔案中新增一個實現BeanPostProcessor介面的類的bean,相當於給所有的bean新增上了後置處理器

一共七步

第一步、通過無參構造器建立bean的例項物件

第二步、對bean的set()方法進行屬性賦值以及對其他bean引用

第三步、呼叫後置處理器中的postProcessBeforeInitialization方法

第四步、呼叫bean的初始化方法(通過init-method標籤配置)

第五步、呼叫後置處理器中的postProcessAfterInitialization方法

第六步、bean可以使用了

第七步、當容器關閉時,呼叫bean的銷燬方法(通過destroy-method標籤配置)

public class MyBeanPost implements BeanPostProcessor{
     @Override
     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         System.out.println("在初始化之前執行的方法");
         return bean;
     }
     @Override
     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
         System.out.println("在初始化之後執行的方法");
         return bean;
     }
}

<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>

3.2.6、bean的自動裝配

根據指定裝配規則(屬性名稱或者屬性型別),Spring自動將匹配的屬性值進行注入

bean標籤autowire屬性設定自動裝配,有兩個常用的屬性值

byName:根據屬性名稱注入,要求注入值bean的id和屬性名稱一致

byType:根據屬性型別注入

<bean id="emp" class="com.cuit.autowire.Emp" autowire="byName"></bean>
<!--<bean id="emp" class="com.cuit.autowire.Emp" autowire="byName"></bean>-->
<bean id="dept" class="com.cuit.autowire.Dept"></bean>

3.3、基於註解的方式建立物件

(1)註解是程式碼特殊標記,格式:@註解名稱(屬性名稱=屬性值, 屬性名稱=屬性值..) (2)使用註解,註解作用在類上面,方法上面,屬性上面 (3)使用註解目的:簡化 xml 配置

Spring針對bean管理建立物件提供註解

(1)@Component

(2)@Service 一般用於業務邏輯層

(3)@Controller 一般用於web層

(4)@Repository 一般用於dao層

上面四個註解功能是一樣的,都可以用來建立 bean 例項

第一步、引入Aop的依賴包

第二步、使用註解要開啟元件掃描,前提引入context名稱空間

<!--指定要掃描的包,包下面的註解就會生效-->
<context:component-scan base-package="com.cuit.testdemo"/>

第三步、在類上方使用上面任一註解都可以建立對應的bean

@Service("userService")
public class UserService {
    public void add() {
        System.out.println("Service add.........");
    }
}

註解建立物件小結:首先載入,當讀取到xml檔案中開啟元件掃描,去對應的包中尋找帶有註解的類,並且建立對應的bean例項

3.4、基於註解的方式屬性注入

@Autowired 根據屬性型別進行自動裝配

@Qualifier 根據屬性的名稱進行注入,配合@Autowired一起使用

@Resource 可以根據名稱和型別進行注入 此註解是jdk自帶的,位於javax.annotation.Resource,jdk11已經被移除了

@Value 針對普通資料型別

第一步 把 service 和 dao 物件建立,在 service 和 dao 類新增建立物件註解

第二步 在 service 注入 dao 物件,在 service 類新增 dao 型別屬性,在屬性上面使用註解

@@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("userDao add ..........");
    }
}
@Service
public class UserService {

    @Autowired
    @Qualifier(value = "userDaoImpl")
    private UserDao userDao;

    public void add() {
        System.out.println("Service add.........");
        userDao.add();
    }

}

3.5、完全註解開發

(1)建立一個配置類,代替xml配置檔案

@Configuration //作為配置類,替代xml配置檔案
@ComponentScan(basePackages = {"com.cuit"})
public class SpringConfig {
}

(2) 編寫測試類

和xml配置檔案不同的是在載入配置檔案時,用的是AnnotationConfigApplicationContext

@Test
public void test2(){
    //基於xml配置檔案的方式載入配置檔案
    //ApplicationContext context = new ClassPathXmlApplicationContext("myBean2.xml");
    //基於配置類的方式載入配置檔案
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = context.getBean("userService", UserService.class);
    userService.add();
}

四、AOP

1、AOP(概念)

(1)面向切面程式設計,利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

(2)通俗描述:不通過修改原始碼方式,在主幹功能裡面新增新功能

比如一個登入流程,需要在登入功能基礎上新增一個許可權判斷,可以利用AOP單獨開一個許可權判斷,利用其他方式對原來的登入流程進行增強

image-20211218163507526

2、動態代理

動態代理有兩種

第一種有介面的情況,使用JDK動態代理

第二種沒有介面的情況,使用CGLIB動態代理

JDK動態代理

JDK動態代理基於有介面的情況

interface Car{
    void sell();
}

建立一個被代理類

class Audi implements Car{

    @Override
    public void sell() {
        System.out.println("奧迪賣車出去");
    }
}

建立一個代理類

class CarFactory{
    public static Object getNewInstance(Object audi){
        CarInvocationHandler handler = new CarInvocationHandler(new Audi());
        //利用Proxy中的newProxyInstance方法生成一個被代理物件
        //因為newProxyInstance方法中第三個引數型別為InvocationHandler,所以建立一個新的類實現InvocationHandler介面
        //此方法需要傳三個引數,引數一:實現InvocationHandler介面類的類載入器;引數二:被代理類實現的一些介面;引數三:為此方法的回撥介面,用於呼叫InvocationHandler例項中的invoke方法
        return Proxy.newProxyInstance(audi.getClass().getClassLoader(), audi.getClass().getInterfaces(), handler);
    }
}

class CarInvocationHandler implements InvocationHandler{

    
    //被代理物件引用,invoke方法裡面method需要使用這個被代理物件
    private Object car;
    public CarInvocationHandler(Object car){
        this.car = car;
    }
	
    //InvocationHandler介面中有一個invoke方法,當一個代理例項的方法被呼叫時,代理方法將被編碼並分發到 InvocationHandler介面的invoke方法執行。
    /**
    * invoke() 方法有下面三個引數:
    * proxy :動態生成的代理類
    * method : 與代理類物件呼叫的方法相對應
    * args : 當前 method 方法的引數
	*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("客戶看車");
        //呼叫被代理物件的真實方法
        Object invoke = method.invoke(car, args);
        System.out.println("客戶走人");
        return invoke;
    }
}

測試類

public class JDKProxyTest {

    public static void main(String[] args) {
        Audi audi = new Audi();
        Car newInstance = (Car) CarFactory.getNewInstance(audi);
        //這裡執行sell方法,實際執行的是invoke方法
        newInstance.sell();
    }
}

輸出

客戶看車
奧迪賣車出去
客戶走人

JDK動態代理使用步驟:

  1. 定義一個介面及其實現類;
  2. 自定義 InvocationHandler 並重寫invoke()方法,在 invoke 方法中我們會呼叫原生方法(被代理類的方法)並自定義一些處理邏輯;
  3. 通過 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法建立代理物件;

invoke() 方法: 當我們的動態代理物件呼叫原生方法的時候,最終實際上呼叫到的是 invoke() 方法,然後 invoke() 方法代替我們去呼叫了被代理物件的原生方法。

Java動態代理優缺點:

優點:

1.Java本身支援,不用擔心依賴問題,隨著版本穩定升級;

2.程式碼實現簡單;

缺點:

1.目標類必須實現某個介面,換言之,沒有實現介面的類是不能生成代理物件的;

2.代理的方法必須都宣告在介面中,否則,無法代理;

3.執行速度效能相對cglib較低;

CGLIB動態代理

CGLIB(Code Generation Library)是一個基於ASM的位元組碼生成庫,它允許我們在執行時對位元組碼進行修改和動態生成。CGLIB通過繼承方式實現代理。

在 CGLIB 動態代理機制中 MethodInterceptor 介面和 Enhancer 類是核心。

需要自定義 MethodInterceptor 並重寫 intercept 方法,intercept 用於攔截增強被代理類的方法。

引入CGLIB的依賴包

<!-- cglib 動態代理依賴 begin -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version></dependency>
<!-- cglib 動態代理依賴 stop -->

建立目標類

class Dog{
    public void call(){
        System.out.println("狗---汪汪");
    }
    final public void run() {
        System.out.println("狗----run");
    }
}

代理類需要實現MethodInterceptor介面,方法呼叫會被轉發到該類的intercept()方法。

intercept方法中有四個引數

  1. o 代表Cglib 生成的動態代理類 物件本身
  2. method 代理類中被攔截的介面方法 Method 例項
  3. objects 介面方法引數
  4. methodProxy 用於呼叫父類真正的業務類方法。可以直接呼叫被代理類介面方法
public class CGLIBProxy implements MethodInterceptor {
    //用於生成 Cglib 動態代理類工具方法
    //zclass 代表需要 被代理的 委託類的 Class 物件
    public Object getInstance(Class zclass){
        //建立加強器,用來建立動態代理類
        Enhancer enhancer = new Enhancer();
        //為代理類指定需要代理的類,也即是父類
        enhancer.setSuperclass(zclass);
        //設定方法攔截器回撥引用,對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實現intercept()方法進行攔截
        enhancer.setCallback(new CGLIBProxy());
        // 獲取動態代理類物件並返回
        return enhancer.create();
    }
    /**
    * o   代表Cglib 生成的動態代理類物件本身
	* method 代理類中被攔截的介面方法Method 例項
	* objects   介面方法引數
	* methodProxy 用於呼叫父類真正的業務類方法。可以直接呼叫被代理類介面方法
	*/
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("呼叫一些增強的方法");
        Object invokeSuper = methodProxy.invokeSuper(o, objects);
        //方法實質都是呼叫父類方法
        //Object invoke = method.invoke(o, objects);
        System.out.println("方法執行完成");
        return invokeSuper;
    }
}

測試類

public class CGLIBProxyTest{
    public static void main(String[] args) {
        CGLIBProxy cGLIBProxy = new CGLIBProxy();
        Dog instance = (Dog) cGLIBProxy .getInstance();
        instance.call();
        instance.run();
    }
}

輸出

呼叫一些增強的方法
狗---汪汪
方法執行完成
狗----run

CGLIB動態代理使用步驟

  1. 定義一個類;
  2. 自定義 MethodInterceptor 並重寫 intercept 方法,intercept 用於攔截增強被代理類的方法,和 JDK 動態代理中的 invoke 方法類似;
  3. 通過 Enhancer 類的 create()建立代理類;

CGLIB優缺點:

優點:

1.代理的類無需實現介面;

2.執行速度相對JDK動態代理較高;

缺點:

1.位元組碼庫需要進行更新以保證在新版java上能執行;

2.動態建立代理物件的代價相對JDK動態代理較高;

3.代理的物件不能是final關鍵字修飾的

參考部落格

https://cloud.tencent.com/developer/article/1461796

https://nilzzzz.github.io/2020/08/JDK+-CGLIB動態代理實戰/

3、AOP術語

連線點:可以被增強的方法

切入點:實際被增強的方法

通知(增強):實際增強的邏輯部分

通知的型別:

  • 前置通知
  • 後置通知
  • 環繞通知
  • 異常通知
  • 最終通知

切面:把通知應用到切入點的過程

4、Spring中AOP操作

Spring框架一般都是基於AspectJ實現AOP操作

AspectJ不是Spring的組成部分,獨立AOP框架,一般把AspectJ和Spring一起使用,進行AOP操作

基於 AspectJ 實現 AOP 操作有兩種方式 (1)基於 xml 配置檔案實現 (2)基於註解方式實現(使用)

基於註解方式實現AOP操作

建立一個配置類

@Configuration //作為配置類,替代xml配置檔案
@EnableAspectJAutoProxy //註冊AspectJ 的自動代理預設為false啟用JDK動態代理,為true強制使用CGLIB動態代理
@ComponentScan(basePackages = {"com.cuit.aop"})
public class SpringConfig {
}

建立一個被代理類User

@Component("user")
public class User {
    public void add() {
        System.out.println("add........");
    }
}

建立一個代理類,並且在類上新增@Aspect註解,將當前類指定為一個切面

@Component
@Aspect
public class UserProxy {
    //具有相同切入點時,在每一個通知裡面的註解裡面寫切入點太麻煩,可以用@Pointcut註解統一設定,然後通知註解裡面可以傳帶有@Pointcut的方法名
    //切入點設定
    @Pointcut("execution(* com.cuit.aop.User.add(..))")
    public void pointcut(){

    }
    
    //@Before前置通知,目標方法前執行
    @Before("pointcut")
    public void berfore(){
        System.out.println("berfore前置輸出");
    }
    //@AfterReturning後置通知,目標方法執行完執行
    @AfterReturning("execution(* com.cuit.aop.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning後置輸出");
    }
    //@AfterThrowing異常通知,目標方法丟擲錯誤時執行
    @AfterThrowing("execution(* com.cuit.aop.User.add(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing丟擲錯誤時輸出");
    }
    //@Around環繞通知,目標方法前後都會執行
    @Around("execution(* com.cuit.aop.User.add(..))")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("around環繞前輸出");
        point.proceed();
        System.out.println("around環繞後輸出");
    }
    //@After最終通知,目標方法執行完執行
    @After("execution(* com.cuit.aop.User.add(..))")
    public void after(){
        System.out.println("after最終輸出");
    }
}

測試類

@Test
public void test3(){
    //ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    User user = context.getBean("user", User.class);
    user.add();
}

執行結果

around環繞前輸出
berfore前置輸出
add........
afterReturning後置輸出
after最終輸出
around環繞後輸出

基於xml方式實現AOP操作

<!--新增掃描-->
<context:component-scan base-package="com.cuit.aop"/>
<!--建立物件-->
<bean id="book" class="com.cuit.aop.User"></bean>
<bean id="bookProxy" class="com.cuit.aop.UserProxy"></bean>
<!--開啟AspectJ自動代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--配置aop增強-->
<aop:config>
    <!--配置切入點-->
    <aop:pointcut id="p" expression="execution(* com.cuit.aop.User.add(..))"/>
    <!--配置切面-->
    <aop:aspect>
        <!--增強作用在具體的方法上-->
        <aop:before method="before" pointcut-ref="p"></aop:before>
    </aop:aspect>
</aop:config>

多個代理類設定優先順序

在代理類上加上註解@Order(數值型別)數值從0開始,數值越小,優先順序越高

五、Spring事務管理

事務操作(事務概念)

事務是資料庫操作最基本單元,邏輯上一組操作,要麼都成功,如果有一個失敗所有操作都失敗

典型場景:銀行轉賬

事務四個特性(ACID)

(1)原子性 (Atomicity)原子性是指事務是一個不可分割的工作單位,事務中的操作要麼全部成功,要麼全部失敗。

(2)一致性 (Consistency)

官網上事務一致性的概念是:事務必須使資料庫從一個一致性狀態變換到另外一個一致性狀態。

換一種方式理解就是:事務按照預期生效,資料的狀態是預期的狀態。

(3)隔離性 (Isolation)

事務的隔離性是多個使用者併發訪問資料庫時,資料庫為每一個使用者開啟的事務,不能被其他事務的運算元據所干擾,多個併發事務之間要相互隔離。

(4)永續性(Durability)

永續性是指一個事務一旦被提交,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響。

在 Spring 進行事務管理操作有兩種方式:程式設計式事務管理和宣告式事務管理(使用)

註解宣告事務管理

操作步驟如下

1、在spring配置檔案中新增事務管理器,也就是下面的transactionManager

2、引入tx名稱空間,開啟事務註解

<context:component-scan base-package="com.cuit"></context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/db" />
    <property name="username" value="username" />
    <property name="password" value="password" />
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!-- JdbcTemplate 物件 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入 dataSource-->
    <property name="dataSource" ref="dataSource"></property><!--set方式注入-->
</bean>
<!--建立事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--開啟事務註解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

3、在 service 類上面(或者 service 類裡面方法上面)新增事務註解@Transactional

如果把這個註解新增類上面,這個類裡面所有的方法都新增事務
如果把這個註解新增方法上面,為這個方法新增事務

@Service
@Transactional
public class UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void change(){
        userDao.update(1,100);
        //int i = 10/0;
        userDao.update(2,-100);
    }
}

@Repository()
public class UserDaoImpl implements UserDao {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Override
    public void update(int id, int value) {
        String sql = "update test set money = money + ? where id = ?";
        int update = jdbcTemplate.update(sql, value, id);
    }
}

1、在 service 類上面新增註解@Transactional,在這個註解裡面可以配置事務相關引數

2、propagation:事務傳播行為
(1)多事務方法直接進行呼叫,這個過程中事務是如何進行管理的

事務傳播行為用來描述由某一個事務傳播行為修飾的方法被巢狀進另一個方法時事務如何傳播。

事務傳播行為型別 說明
PROPAGATION_REQUIRED 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。
PROPAGATION_SUPPORTS 支援當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY 使用當前的事務,如果當前沒有事務,就丟擲異常。
PROPAGATION_REQUIRES_NEW 新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER 以非事務方式執行,如果當前存在事務,則丟擲異常。
PROPAGATION_NESTED 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

具體情況可以參考下面連結

Spring事務傳播行為詳解 - SegmentFault 思否

3、ioslation:事務隔離級別

(1)事務有隔離性,多事務操作之間不會產生影響。不考慮隔離性產生很多問題
(2)有三個讀問題:髒讀、不可重複讀、虛(幻)讀

髒讀:事務A修改了表中的一行資料,但沒有提交,這時候事務B讀取了被事務A修改後的資料,之後事務A因為某種原因回滾,那麼事務B讀取的資料就是髒的

不可重複讀:事務A先讀取了事務B修改前的資料,然後事務B提交了,這時事務A又去讀了一次資料,結果讀到的是事務B修改後的資料,產生了一次事務中讀取資料結果不一致的現象,為不可重複讀

幻讀:事務A先讀取了事務B新增前的資料,然後事務B提交了,這時事務A又去讀了一次資料,結果讀到的是事務B新增後的資料,產生了一次事務中讀取資料條數結果不一致的現象,為幻讀

(3)通過設定事務隔離級別,解決讀問題

不同隔離級別存在不同的問題

隔離級別 髒讀 不可重複讀 幻讀
Read uncommitted
Read committed ×
Repeatable read × ×
Serializable × × ×

4、timeout:超時時間
(1)事務需要在一定時間內進行提交,如果不提交進行回滾
(2)預設值是 -1 ,設定時間以秒單位進行計算
5、readOnly:是否只讀
(1)讀:查詢操作,寫:新增修改刪除操作
(2)readOnly 預設值 false,表示可以查詢,可以新增修改刪除操作
(3)設定 readOnly 值是 true,設定成 true 之後,只能查詢
6、rollbackFor:回滾
(1)設定出現哪些異常進行事務回滾
7、noRollbackFor:不回滾
(1)設定出現哪些異常不進行事務回滾

xml宣告事務管理

1、在 spring 配置檔案中進行配置
第一步 配置事務管理器
第二步 配置通知
第三步 配置切入點和切面,需要引入AOP名稱空間

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/book" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
    </bean>
    <!-- JdbcTemplate 物件 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入 dataSource-->
        <property name="dataSource" ref="dataSource"></property><!--set方式注入-->
    </bean>
    <!--建立事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置通知-->
    <tx:advice id="interceptor">
        <tx:attributes>
            <tx:method name="select" isolation="DEFAULT"></tx:method>
            <tx:method name="update" isolation="READ_COMMITTED"></tx:method>
        </tx:attributes>
    </tx:advice>
    <!--配置切入點和切面-->
    <aop:config>
        <aop:pointcut id="pc" expression="execution(* com.cuit.service.UserService.*(..))"/>
        <aop:advisor advice-ref="interceptor" pointcut-ref="pc"></aop:advisor>
    </aop:config>

完全註解宣告事務管理

建立配置類,使用配置類替代 xml 配置檔案

@Configuration
@ComponentScan("com.cuit") //元件掃描
@EnableTransactionManagement //開啟事務
public class StartConfig {

    //建立資料庫連線池
    @Bean
    public DruidDataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///user_db");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    //建立 JdbcTemplate 物件
    @Bean
    public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

   	//建立事務管理器
    @Bean
    public DataSourceTransactionManager getTransactionManager(DruidDataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }

}

六、Spring5新特性

1、整個 Spring5 框架的程式碼基於 Java8,執行時相容 JDK9,許多不建議使用的類和方法在程式碼庫中刪除
2、Spring 5.0 框架自帶了通用的日誌封裝
(1)Spring5 已經移除 Log4jConfigListener,官方建議使用 Log4j2
(2)Spring5 框架整合 Log4j2

第一步 引入 jar 包

第二步 建立 log4j2.xml 配置檔案,這個檔名稱是固定的

<?xml version="1.0" encoding="UTF-8"?>
<!--日誌級別以及優先順序排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE >
ALL -->
<!--Configuration 後面的 status 用於設定 log4j2 自身內部的資訊輸出,可以不設定,
當設定成 trace 時,可以看到 log4j2 內部各種詳細輸出-->
<configuration status="INFO">
    <!--先定義所有的 appender-->
    <appenders>
        <!--輸出日誌資訊到控制檯-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日誌輸出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-
5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然後定義 logger,只有定義 logger 並引入的 appender,appender 才會生效-->
    <!--root:用於指定專案的根日誌,如果沒有單獨指定 Logger,則會使用 root 作為
    預設的日誌輸出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

3、Spring5 框架核心容器支援@Nullable 註解

實質是告訴編譯器,接受空值,並且如果重寫該方法,則還應接受空值。,

1)@Nullable 註解可以使用在方法上面,屬性上面,引數上面,表示方法返回可以為空,屬性值可以
為空,引數值可以為空
2)註解用在方法上面,方法返回值可以為空
3)註解使用在方法引數裡面,方法引數可以為空
4)註解使用在屬性上面,屬性值可以為空

4、Spring5 核心容器支援函式式風格 GenericApplicationContext

5、Spring5 支援整合 JUnit5

第一步 引入 JUnit5 的 jar 包
第二步 建立測試類,使用註解完成

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean.xml")
public class JTest5 {
	@Autowired
	private UserService userService;
	@Test
	public void test1() {
	userService.accountMoney();
	}
}

//也可以使用複合註解代替上面兩個
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JTest5 {
	@Autowired
	private UserService userService;
	@Test
	public void test1() {
	userService.accountMoney();
	}
}

相關文章