狂神說Spring課堂筆記


Spring學習筆記
1、Spring
1.1 簡介
1.2 優點
1.3 組成
1.4 拓展
2、IOC理論推導
IOC本質
3、HelloSpring
4、IOC創建對象的方式
5、Spring配置
5.1 別名
5.2 Bean的配置
5.3 import
6、依賴注入
6.1 構造器注入
6.2 Set方式注入【重點】
6.3 拓展方式注入
6.4 bean的作用域
7、Bean的自動裝配
7.1 測試
7.2 ByName自動裝配
7.3 ByType自動裝配
7.4 使用注解實現自動裝配
8、使用注解開發
9、使用Java的方式配置Spring
10、代理模式
10.1 靜態代理
10.2 加深理解
10.3 動態代理
11、AOP
11.1 什么是AOP
11.2 AOP在Spring中的作用
11.3 使用Spring實現AOP
12、整合Mybatis(2020-11-21)
12.1 回憶mybatis
12.2 Mybatis-Spring
13、聲明式事務
13.1 回顧事務
13.2 Spring中的事務管理

1、Spring(2020-11-9)

1.1 簡介
Spring:春天------>給軟件行業帶來了春天!
2002,首次推出了Spring框架的雛形:interface21框架!
Spring框架即以interface21框架為基礎,經過重新設計,並不斷豐富其內涵,於2004年3月24日發布了1.0正式版。
Rod Johnson,Spring Framework創始人,著名作者。很難想象Rod Johnson的學歷,真的讓好多人大吃一驚,他是悉尼大學的博士,然而他的專業不是計算機,而是音樂學。
Spring理念:使現有的技術更加容易使用,本身是一個大雜燴,整合了現有的技術框架!
SSH:Struct2 + Spring + Hibernate!
SSM:SpringMVC + Spring + Mybatis!
官網:https://spring.io/projects/spring-framework#overview
官方下載地址:https://repo.spring.io/release/org/springframework/spring/
GitHub:https://github.com/spring-projects/spring-framework

org.springframework spring-webmvc 5.2.0.RELEASE org.springframework spring-jdbc 5.2.0.RELEASE

1.2 優點
Spring是一個開源的免費的框架(容器)!
Spring是一個輕量級的、非入侵式的框架!
控制反轉(IOC),面向切面編程(AOP)!
支持事務的處理,對框架整合的支持!
總結一句話:Spring就是一個輕量級的控制反轉(IOC)和面向切面編程(AOP)的框架!
1.3 組成

1.4 拓展

現代化的Java開發!說白就是基於Spring的開發!
Spring Boot
一個快速開發的腳手架。
基於SpringBoot可以快速的開發單個微服務。
約定大於配置。
Spring Cloud
SpringCloud是基於SpringBoot實現的。
因為現在大多數公司都在使用SpringBoot進行快速開發,學習SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上啟下的作用!
弊端:發展了太久之后,違背了原來的理念!配置十分繁瑣,人稱:“配置地獄!”
2、IOC理論推導
UserDao 接口

public interface UserDao {
    void getUser();
}

UserDaoImpl 實現類

public class UserDaoImpl implements UserDao {
    public void getUser() {
        System.out.println("默認獲取用戶數據");
    }
}

UserService 業務接口

public interface UserService {
    void getUser();
}

UserServiceImpl 業務實現類

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();

    public void getUser() {
        userDao.getUser();
    }
}

測試

public class MyTest {
    public static void main(String[] args) {

        //用戶實際調用的是業務層,dao層他們不需要接觸!
        UserService userService = new UserServiceImpl();
        userService.getUser();
    }
}

在我們之前的業務中,用戶的需求可能會影響我們原來的代碼,我們需要根據用戶的需求去修改原代碼!如果程序代碼量十分大,修改一次的成本代價十分昂貴!
我們使用一個Set接口實現,已經發生了革命性的變化!
```
private UserDao userDao;

//利用set進行動態實現值的注入!
public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
}

``
之前,程序是主動創建對象!控制權在程序猿手上!
使用了set注入后,程序不再具有主動性,而是變成了被動的接收對象!
這種思想,從本質上解決了問題,我們程序猿不用再去管理對象的創建了。系統的耦合性大大降低~,可以更加專注的在業務的實現上!這是IOC的原型!

IOC本質
控制反轉IoC(Inversion of Control),是一種設計思想,DI(依賴注入)是實現IoC的一種方法,也有人認為DI只是IoC的另一種說法。沒有IoC的程序中,我們使用面向對象編程,對象的創建與對象間的依賴關系完全硬編碼在程序中,對象的創建由程序自己控制,控制反轉后將對象的創建轉移給第三方,個人認為所謂控制反轉就是:獲得依賴對象的方式反轉了。

采用XML方式配置Bean的時候,Bean的定義信息是和實現分離的,而采用注解的方式可以把兩者合為一體,Bean的定義信息直接以注解的形式定義在實現類中,從而達到了零配置的目的。
控制反轉是一種通過描述(XML或注解)並通過第三方去生產或獲取特定對象的方式。在Spring中實現控制反轉的是IoC容器,其實現方法是依賴注入(Dependency Injection,DI)。

3、HelloSpring
新建一個maven項目,編寫實體類

public class Hello {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}

編寫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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--使用Spring來創建對象,在Spring這些都稱為Bean
    類型 變量名 = new 類型();
    Hello hello = new Hello();

    id = 變量名
    class = new的對象
    property 相當於給對象中的屬性設置一個值!
        -->
    <bean id="hello" class="com.kuang.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>
</beans>

測試

public class MyTest {
    public static void main(String[] args) {
        //獲取Spring的上下文對象!
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        //我們的對象現在都在Spring中的管理了,我們需要使用,直接去里面取出來就可以!
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

思考問題?

Hello對象是誰創建的?
Hello對象是由Spring創建的。
Hello對象的屬性是怎么設置的?
Hello對象的屬性是由Spring容器設置的。
這個過程就叫控制反轉:

控制:誰來控制對象的創建,傳統應用程序的對象是由程序本身控制創建的,使用Spring后,對象是由Spring來創建的。

反轉:程序本身不創建對象,而變成被動的接收對象。

依賴注入:就是利用set方法來進行注入的。

IOC是一種編程思想,由主動的編程變成被動的接收。

可以通過new ClassPathXmlApplicationContext去瀏覽一下底層源碼。

OK,到了現在,我們徹底不用在程序中去改動了,要實現不同的操作,只需要在xml配置文件中進行修改,所謂的IOC,一句話搞定:對象由Spring來創建,管理,裝配!

4、IOC創建對象的方式(2020-11-10)
使用無參構造創建對象,默認!
假設我們要使用有參構造創建對象。
下標賦值

<!--第一種方式:下標賦值    -->
<bean id="user" class="com.kuang.pojo.User">
    <constructor-arg index="0" value="狂神說Java"/>
</bean>

類型

<!--第二種方式:通過類型的創建,不建議使用    -->
<bean id="user" class="com.kuang.pojo.User">
    <constructor-arg type="java.lang.String" value="lifa"/>
</bean>

參數名

<!--第三種方式:直接通過參數名來設置    -->
<bean id="user" class="com.kuang.pojo.User">
    <constructor-arg name="name" value="李發"/>
</bean>

總結:在配置文件加載的時候,容器中管理的對象就已經初始化了!

5、Spring配置
5.1 別名

<!--別名,如果添加了別名,我們也可以使用別名獲取到這個對象-->
 <alias name="user" alias="userNew"/>

5.2 Bean的配置
```

<bean id="userT" class="com.kuang.pojo.UserT" name="user2 u2,u3;u4">
    <property name="name" value="黑心白蓮"/>
</bean>

5.3 import
這個import。一般用於團隊開發使用,它可以將多個配置文件,導入合並為一個。
假設,現在項目中有多個人開發,這三個人負責不同的類開發,不同的類需要注冊在不同的bean中,我們可以利用import將所有人的beans.xml合並為一個總的!

張三
李四
王五

applicationContext.xml



使用的時候,直接使用總的配置就可以了。

6、依賴注入
6.1 構造器注入
前面已經介紹過,參考4、IOC創建對象的方式

6.2 Set方式注入【重點】
依賴注入:Set注入
依賴:bean對象的創建依賴於容器!
注入:bean對象中的所有屬性,由容器來注入!
【環境搭建】

復雜類型

public class Address {
private String address;

public String getAddress() {
    return address;
}

public void setAddress(String address) {
    this.address = address;
}

}



真實測試對象

public class Student {

private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;

}



beans.xml

<bean id="student" class="com.kuang.pojo.Student">
    <!--第一種:普通值注入,value        -->
    <property name="name" value="黑心白蓮"/>
</bean>
```

測試類

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        Student student = (Student) context.getBean("student");
        System.out.println(student.getName());
    }
}

完善注入信息
```


<bean id="student" class="com.kuang.pojo.Student">
    <!--第一種:普通值注入,value        -->
    <property name="name" value="黑心白蓮"/>

    <!--第二種:        -->
    <property name="address" ref="address"/>

    <!--數組        -->
    <property name="books">
        <array>
            <value>紅樓夢</value>
            <value>西游記</value>
            <value>水滸傳</value>
            <value>三國演義</value>
        </array>
    </property>

    <!--List        -->
    <property name="hobbies">
        <list>
            <value>打籃球</value>
            <value>看電影</value>
            <value>敲代碼</value>
        </list>
    </property>

    <!--Map        -->
    <property name="card">
        <map>
            <entry key="身份證" value="123456789987456321"/>
            <entry key="銀行卡" value="359419496419481649"/>
        </map>
    </property>

    <!--Set        -->
    <property name="games">
        <set>
            <value>LOL</value>
            <value>COC</value>
            <value>BOB</value>
        </set>
    </property>

    <!--NULL        -->
    <property name="wife">
        <null/>
    </property>

    <!--Properties        -->
    <property name="info">
        <props>
            <prop key="driver">20191029</prop>
            <prop key="url">102.0913.524.4585</prop>
            <prop key="user">黑心白蓮</prop>
            <prop key="password">123456</prop>
        </props>
    </property>

</bean>

6.3 拓展方式注入
我們可以使用p命名空間和c命名空間進行注入
官方解釋:

使用:

<!--p命名空間注入,可以直接注入屬性的值:property-->
<bean id="user" class="com.kuang.pojo.User" p:name="黑心白蓮" p:age="20"/>

<!--c命名空間注入,通過構造器注入:constructor-args-->
<bean id="user2" class="com.kuang.pojo.User" c:name="狂神" c:age="22"/>
```

測試:

```

@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");

    User user = context.getBean("user",User.class);
    System.out.println(user);

    User user2 = context.getBean("user2",User.class);
    System.out.println(user2);
}


注意點:p命名和c命名空間不能直接使用,需要導入xml約束!

       ```
xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"

6.4 bean的作用域

單例模式(Spring默認機制)
<bean id="user2" class="com.kuang.pojo.User" c:name="狂神" c:age="22" scope="singleton"/>

原型模式:每次從容器中get的時候,都會產生一個新對象!
<bean id="user2" class="com.kuang.pojo.User" c:name="狂神" c:age="22" scope="prototype"/>

其余的request、session、application、這些只能在web開發中使用到!
7、Bean的自動裝配(2020-11-11)
自動裝配是Spring滿足bean依賴一種方式!
Spring會在上下文中自動尋找,並自動給bean裝配屬性!
在Spring中有三種裝配的方式:

在xml中顯式的配置;
在java中顯式配置;
隱式的自動裝配bean【重要】
7.1 測試
環境搭建:創建項目,一個人有兩個寵物!

```
<bean id="people" class="com.kuang.pojo.People">
    <property name="name" value="小白蓮"/>
    <property name="cat" ref="cat"/>
    <property name="dog" ref="dog"/>
</bean>


7.2 ByName自動裝配
        ```
<!--
        byName:會自動在容器上下文中查找,和自己對象set方法后面的值對應的bean id!
            -->
        <bean id="people" class="com.kuang.pojo.People" autowire="byName">
            <property name="name" value="小白蓮"/>
        </bean>

7.3 ByType自動裝配
```

    <bean id="people" class="com.kuang.pojo.People" autowire="byType">
        <property name="name" value="小白蓮"/>
    </bean>


小結:

ByName的時候,需要保證所有bean的id唯一,並且這個bean需要和自動注入的屬性的set方法的值一致!
ByType的時候,需要保證所有bean的class唯一,並且這個bean需要和自動注入的屬性的類型一致!
7.4 使用注解實現自動裝配
jdk1.5支持的注解,Spring2.5就支持注解了!

要使用注解須知:

導入約束
配置注解的支持

	<!--開啟注解的支持    -->
    <context:annotation-config/>
```

@Autowired

直接在屬性上使用即可!也可以在set方法上使用!

使用Autowired我們就可以不用編寫set方法了,前提是你這個自動配置的屬性在IOC(Spring)容器中存在,且符合名字ByName!

科普:

@Nullable 字段標記了了這個注解,說明這個字段可以為null;

public @interface Autowired {
    boolean required() default true;
}

測試代碼

public class People {
    //如果顯式定義了Autowired的required屬性為false,說明這個對象可以為null,否則不允許為空
    @Autowired(required = false)
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
}

如果@Autowired自動裝配的環境比較復雜,自動裝配無法通過一個注解【@Autowired】完成的時候,我們可以使用@Qualifier(value = “xxx”)去配置@Autowired的使用,指定一個唯一的bean對象注入!

public class People {
    @Autowired
    @Qualifier(value = "cat111")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog222")
    private Dog dog;
    private String name;
}

@Resource

public class People {

    @Resource
    private Cat cat;

    @Resource
    private Dog dog;
}

小結:

@Resource和@Autowired的區別:

都是用來自動裝配的,都可以放在屬性字段上
@Autowired通過byType的方式實現,而且必須要求這個對象存在!【常用】
@Resource默認通過byName的方式實現,如果找不到名字,則通過byType實現!如果兩個都找不到的情況下,就報錯!【常用】
執行順序不同:@Autowired通過byType的方式實現。
8、使用注解開發
在Spring4之后,要使用注解開發,必須要保證aop的包導入了

使用注解需要導入約束,配置注解的支持!

```
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	        https://www.springframework.org/schema/beans/spring-beans.xsd
	        http://www.springframework.org/schema/context
	        https://www.springframework.org/schema/context/spring-context.xsd">
		
		<!--開啟注解的支持    -->
        <context:annotation-config/>
</beans>


bean

屬性如何注入

//等價於
//@Component 組件

@Component
public class User {

//相當於  <property name="name" value="白蓮"/>
@Value("白蓮")
public String name;

}



衍生的注解
@Component有幾個衍生注解,我們在web開發中,會按照mvc三層架構分層!

dao 【@Repository】
service 【@Service】
controller 【@Controller】

這四個注解功能都是一樣的,都是代表將某個類注冊到Spring中,裝配Bean

自動裝配

- @Autowired:自動裝配通過類型,名字。如果Autowired不能唯一自動裝配上屬性,則需要通過@Qualifier(value = "xxx")去配置。
- @Nullable 字段標記了了這個注解,說明這個字段可以為null;
- @Resource:自動裝配通過名字,類型。

作用域

@Component
@Scope("singleton")
public class User {

//相當於  <property name="name" value="白蓮"/>
@Value("白蓮")
public String name;

}



小結
xml與注解:
xml更加萬能,適用於任何場合!維護簡單方便
注解不是自己類使用不了,維護相隊復雜!
xml與注解最佳實踐:
xml用來管理bean;
注解只負責完成屬性的注入;
我們在使用的過程中,只需要注意一個問題:必須讓注解生效,就需要開啟注解的支持
    ```
<!--指定要掃描的包,這個包下的注解就會生效-->
    <context:component-scan base-package="com.kuang"/>
    <!--開啟注解的支持    -->
    <context:annotation-config/>

9、使用Java的方式配置Spring
我們現在要完全不使用Spring的xml配置了,全權交給Java來做!
JavaConfig是Spring的一個子項目,在Spring4之后,它成為了一個核心功能!

實體類

//這里這個注解的意思,就是說明這個類被Spring接管了,注冊到了容器中
@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }

    @Value("黑心白蓮") //屬性注入值
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

配置文件

// 這個也會Spring容器托管,注冊到容器中,因為它本來就是一個@Component
// @Configuration代表這是一個配置類,就和我們之前看的beans.xml
@Configuration
@ComponentScan("com.kuang.pojo")
@Import(KuangConfig2.class)
public class KuangConfig {

    // 注冊一個bean,就相當於我們之前寫的一個bean標簽
    // 這個方法的名字,就相當於bean標簽中id屬性
    // 這個方法的返回值,就相當於bean標簽中的class屬性
    @Bean
    public User user(){
        return new User(); // 就是返回要注入到bean的對象!
    }
}

測試類

public class MyTest {
    public static void main(String[] args) {

        //如果完全使用了配置類方式去做,我們就只能通過 AnnotationConfig 上下文來獲取容器,通過配置類的class對象加載!
        ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.class);

        User user = context.getBean("user", User.class);
        System.out.println(user.getName());
    }
}

這種純Java的配置方式,在SpringBoot中隨處可見!

10、代理模式(2020-11-12)(2020-11-20)
由於開題答辯耽擱了1個禮拜時間,現在從這回顧繼續學習!!!
為什么要學習代理模式?因為這就是SpringAOP的底層!【SpringAOP和SpringMVC】

代理模式的分類:

靜態代理
動態代理

10.1 靜態代理
角色分析:

抽象角色:一般會使用接口或者抽象類來解決
真實角色:被代理的角色
代理角色:代理真實角色,代理真實角色后,我們一般會做一些附屬操作
客戶:訪問代理對象的人!
代碼步驟:

接口

//租房
public interface Rent {
    public void rent();
}

真實角色

//房東
public class Host implements Rent{
    public void rent() {
        System.out.println("房東出租房子!");
    }
}

代理角色

public class Proxy implements Rent{
    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {
        this.host = host;
    }

    public void rent() {
        host.rent();
        seeHouse();
        sign();
        fee();
    }

    //看房
    public void seeHouse(){
        System.out.println("中介帶着看房子!");
    }

    //簽合同
    public void sign(){
        System.out.println("和中介簽署租賃合同!");
    }

    //收費用
    public void fee(){
        System.out.println("中介收取費用!");
    }
}

客戶端訪問代理角色

public class Client {
    public static void main(String[] args) {
        //房東要出租房子
        Host host = new Host();
//        host.rent();

        //代理,中介幫房東出租房子,並且代理角色一般會有一些附屬操作!
        Proxy proxy = new Proxy(host);

        //不用面對房東,直接找中介租房即可!
        proxy.rent();
    }
}

代理模式的好處:

可以使真實角色的操作更加純粹!不用去關注一些公共的業務
公共角色就交給代理角色!實現了業務的分工!
公共業務發生擴展的時候,方便集中管理!
缺點:

一個真實角色就會產生一個代理角色,代碼量會翻倍,開發效率會變低~
10.2 加深理解
代碼步驟:

接口

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}

真實角色

//真實角色
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加了一個用戶!");
    }

    public void delete() {
        System.out.println("刪除了一個用戶!");
    }

    public void update() {
        System.out.println("修改了一個用戶!");
    }

    public void query() {
        System.out.println("查詢了一個用戶!");
    }
}

代理角色

public class UserServiceProxy implements UserService{
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    public void add() {
        log("add");
        userService.add();
    }

    public void delete() {
        log("delete");
        userService.delete();
    }

    public void update() {
        log("update");
        userService.update();
    }

    public void query() {
        log("query");
        userService.query();
    }

    public void log(String msg){
        System.out.println("[Debug] 使用了一個"+msg+"方法");
    }
}

客戶端訪問代理角色

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();

        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);

        proxy.delete();
    }
}

聊聊AOP

10.3 動態代理
動態代理和靜態代理角色一樣
動態代理的代理類是動態生成的,不是我們直接寫好的!
動態代理分為兩大類:基於接口的動態代理,基於類的動態代理
基於接口 — JDK動態代理【我們在這里使用】
基於類:cglib
java字節碼實現:javassist
需要了解兩個類:Proxy:代理;InvocationHandler:調用處理程序。

代碼步驟:

接口

public interface Rent {
    public void rent();
}

真實角色

public class Host implements Rent{
    public void rent() {
        System.out.println("房東要出租房子!");
    }
}

ProxyInvocationHandler類

//我們會用這個類,自動生成代理類!
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //生成得到代理類
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),this);
    }

    //處理代理實例,並返回結果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //動態代理的本質,就是使用反射機制實現!
        Object result = method.invoke(rent, args);
        seeHose();
        fee();
        return result;
    }

    public void seeHose(){
        System.out.println("中介帶着看房子!");
    }

    public void fee(){
        System.out.println("中介收取費用!");
    }
}

測試

public class Client {
    public static void main(String[] args) {
        //真實角色
        Host host = new Host();

        //代理角色:現在沒有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();

        //通過調用程序處理角色來處理我們要調用的接口對象!
        pih.setRent(host);
        Rent proxy = (Rent) pih.getProxy(); //這里的proxy就是動態生成的,我們並沒有寫
        proxy.rent();

    }
}

在此,我們可以提煉出ProxyInvocationHandler作為工具類

//用這個類自動生成代理類!
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成得到代理類
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    //處理代理實例,並返回結果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }

    public void log(String msg){
        System.out.println("[Debug] 使用了一個"+msg+"方法");
    }
}

動態代理的好處:

可以使真實角色的操作更加純粹!不用去關注一些公共的業務
公共角色就交給代理角色!實現了業務的分工!
公共業務發生擴展的時候,方便集中管理!
一個動態代理類代理的是一個接口,一般就是對應的一類業務
一個動態代理類可以代理多個類,只要是實現了同一個接口即可!
11、AOP
11.1 什么是AOP
AOP(Aspect Oriented Programming)意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。

11.2 AOP在Spring中的作用
提供聲明式事務;允許用戶自定義切面

橫切關注點:跨越應用程序多個模塊的方法或功能。即是,與我們業務邏輯無關的,但是我們需要關注的部分,就是橫切關注點。如日志,安全,緩存,事務等等…
切面(ASPECT):橫切關注點被模塊化的特殊對象。即,它是一個類。
通知(Advice):切面必須要完成的工作。即,它是類中的一個方法。
目標(Target):被通知對象。
代理(Proxy):向目標對象應用通知之后創建的對象。
切入點(PointCut):切面通知執行的“地點”的定義。
連接點(JointPoint):與切入點匹配的執行點。

SpringAOP中,通過Advice定義橫切邏輯,Spring中支持5種類型的Advice:

即AOP在不改變原有代碼的情況下,去增加新的功能。

11.3 使用Spring實現AOP
【重點】使用AOP織入,需要導入一個依賴包!

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

方式一: 使用Spring的API接口【主要是SpringAPI接口實現】

在service包下,定義UserService業務接口和UserServiceImpl實現類

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}

public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加了一個用戶!");
    }

    public void delete() {
        System.out.println("刪除了一個用戶!");
    }

    public void update() {
        System.out.println("更新了一個用戶!");
    }

    public void select() {
        System.out.println("查詢了一個用戶!");
    }
}

在log包下,定義我們的增強類,一個Log前置增強和一個AfterLog后置增強類

public class Log implements MethodBeforeAdvice {

    //method: 要執行的目標對象的方法
    //args:參數
    //target:目標對象
    public void before(Method method, Object[] agrs, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被執行了");
    }
}




public class AfterLog implements AfterReturningAdvice {

    //returnValue: 返回值
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("執行了"+method.getName()+"方法,返回結果為:"+returnValue);
    }
}

最后去spring的文件中注冊 , 並實現aop切入實現 , 注意導入約束,配置applicationContext.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注冊bean-->
    <bean id="userService" class="com.kuang.service.UserServiceImpl"/>
    <bean id="log" class="com.kuang.log.Log"/>
    <bean id="afterLog" class="com.kuang.log.AfterLog"/>

    <!--方式一:使用原生Spring API接口-->
    <!--配置aop:需要導入aop的約束-->
    <aop:config>
        <!--切入點:expression:表達式,execution(要執行的位置!* * * * *)-->
        <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>

        <!--執行環繞增加!-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

測試

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //動態代理代理的是接口:注意點
        UserService userService = (UserService) context.getBean("userService");

        userService.add();
//        userService.select();
    }
}

方式二: 自定義類來實現AOP【主要是切面定義】

在diy包下定義自己的DiyPointCut切入類

public class DiyPointCut {
    public void before(){
        System.out.println("======方法執行前======");
    }

    public void after(){
        System.out.println("======方法執行后======");
    }
}

去spring中配置文件
```

<bean id="diy" class="com.kuang.diy.DiyPointCut"/>

<aop:config>
    <!--自定義切面,ref 要引用的類-->
    <aop:aspect ref="diy">
        <!--切入點-->
        <aop:pointcut id="point" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
        <!--通知-->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>
</aop:config>


測試
方式三: 使用注解實現!

在diy包下定義注解實現的AnnotationPointCut增強類

//聲明式事務!
@Aspect //標注這個類是一個切面
public class AnnotationPointCut {

@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
    System.out.println("====方法執行前====");
}

@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
    System.out.println("====方法執行后====");
}

//在環繞增強中,我們可以給定一個參數,代表我們要獲取處理切入的點;
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable{
    System.out.println("環繞前");

    Signature signature = jp.getSignature();// 獲得簽名
    System.out.println("signature:"+signature);

    Object proceed = jp.proceed(); //執行方法

    System.out.println("環繞后");

    System.out.println(proceed);
}

}



在Spring配置文件中,注冊bean,並增加支持注解的配置。
    ```
<!--方式三:使用注解-->
    <bean id="annotationPointCut" class="com.kuang.diy.AnnotationPointCut"/>
    <!--開啟注解支持! JDK(默認是 proxy-target-class="false")  cglib(proxy-target-class="true")-->
    <aop:aspectj-autoproxy/>

測試
12、整合Mybatis(2020-11-21)
步驟:

導入相關jar包

junit
mybatis
mysql數據庫
spring相關
aop織入器
mybatis-spring整合包【重點】在此還導入了lombok包。
配置Maven靜態資源過濾問題!
```


junit
junit
4.12


org.mybatis
mybatis
3.5.6


mysql
mysql-connector-java
5.1.47


org.springframework
spring-webmvc
5.2.0.RELEASE




org.springframework
spring-jdbc
5.2.0.RELEASE


org.aspectj
aspectjweaver
1.8.13



org.mybatis
mybatis-spring
2.0.2


org.projectlombok
lombok
1.18.10

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
</build>


編寫配置文件
測試
12.1 回憶mybatis
編寫pojo實體類

@Data
public class User {
private int id;
private String name;
private String pwd;
}



編寫實現mybatis的配置文件
```

編寫UserMapper接口

public interface UserMapper {
    public List<User> selectUser();
}

編寫UserMapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mapper.UserMapper">

    <!--sql-->
    <select id="selectUser" resultType="user">
        select * from mybatis.user
    </select>
</mapper>

測試

@Test
public void selectUser() throws IOException {

   String resource = "mybatis-config.xml";
   InputStream inputStream = Resources.getResourceAsStream(resource);
   SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
   SqlSession sqlSession = sqlSessionFactory.openSession();

   UserMapper mapper = sqlSession.getMapper(UserMapper.class);

   List<User> userList = mapper.selectUser();
   for (User user: userList){
       System.out.println(user);
  }

   sqlSession.close();
}

12.2 Mybatis-Spring
什么是MyBatis-Spring?

MyBatis-Spring 會幫助你將 MyBatis 代碼無縫地整合到 Spring 中。

文檔鏈接:http://mybatis.org/spring/zh/index.html

如果使用 Maven 作為構建工具,僅需要在 pom.xml 中加入以下代碼即可:

    ```
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.2</version>
    </dependency>


整合實現一:

引入Spring配置文件spring-dao.xml

<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
https://www.springframework.org/schema/beans/spring-beans.xsd

```

配置數據源替換mybaits的數據源
```

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>


配置SqlSessionFactory,關聯MyBatis
    ```
<!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--關聯mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/kuang/mapper/*.xml"/>
    </bean>

注冊sqlSessionTemplate,關聯sqlSessionFactory
```

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!--只能使用構造器注入sqlSessionFactory,因為它沒有set方法-->
    <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>


需要UserMapper接口的UserMapperImpl 實現類,私有化sqlSessionTemplate

public class UserMapperImpl implements UserMapper {

//我們的所有操作,都使用sqlSession來執行,在原來,現在都使用SqlsessionTemplate
private SqlSessionTemplate sqlSession;

public void setSqlSession(SqlSessionTemplate sqlSession) {
    this.sqlSession = sqlSession;
}

public List<User> selectUser() {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    return mapper.selectUser();
}

}



將自己寫的實現類,注入到Spring配置文件中。
    ```
<bean id="userMapper" class="com.kuang.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

測試使用即可!
```
@Test
public void test () throws IOException {

    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
    
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    for (User user : userMapper.selectUser()) {
        System.out.println(user);
    }
}


結果成功輸出!現在我們的Mybatis配置文件的狀態!發現都可以被Spring整合!

<typeAliases>
    <package name="com.kuang.pojo"/>
</typeAliases>
```

整合實現二:

mybatis-spring1.2.3版以上的才有這個,官方文檔截圖:

dao繼承Support類 , 直接利用 getSqlSession() 獲得 , 然后直接注入SqlSessionFactory . 比起整合方式一 , 不需要管理SqlSessionTemplate , 而且對事務的支持更加友好 . 可跟蹤源碼查看。

測試:

將我們上面寫的UserMapperImpl修改一下

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {

    public List<User> selectUser() {
        
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}

注入到Spring配置文件中。

<bean id="userMapper" class="com.kuang.mapper.UserMapperImpl">
     <property name="sqlSessionFactory" ref="sqlSessionFactory" />
 </bean>

測試

@Test
 public void test () throws IOException {

     ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
     
     UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
     for (User user : userMapper.selectUser()) {
         System.out.println(user);
     }
 }

13、聲明式事務
13.1 回顧事務
把一組業務當成一個業務來做;要么都成功,要么都失敗!
事務在項目開發中,十分的重要,涉及到數據的一致性問題,不能馬虎!
確保完整性和一致性。
事務ACID原則:

原子性(atomicity)
事務是原子性操作,由一系列動作組成,事務的原子性確保動作要么全部完成,要么完全不起作用。
一致性(consistency)
一旦所有事務動作完成,事務就要被提交。數據和資源處於一種滿足業務規則的一致性狀態中。
隔離性(isolation)
可能多個事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。
持久性(durability)
事務一旦完成,無論系統發生什么錯誤,結果都不會受到影響。通常情況下,事務的結果被寫到持久化存儲器中。
測試:

將上面的代碼拷貝到一個新項目中
在之前的案例中,我們給userMapper接口新增兩個方法,刪除和增加用戶;

//添加一個用戶
int addUser(User user);

//根據id刪除用戶
int deleteUser(int id);

UserMapper文件,我們故意把 deletes 寫錯,測試!

<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>

<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>

編寫接口的UserMapperImpl實現類,在實現類中,我們去操作一波

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {


    //增加一些操作
    public List<User> selectUser() {
        User user = new User(5, "小王", "185161");
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        mapper.addUser(user);
        mapper.deleteUser(5);

        return mapper.selectUser();
    }
    
    //新增
    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    //刪除
    public int deleteUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
    }
}

測試

```

@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    for (User user : userMapper.selectUser()) {
        System.out.println(user);
    }
}


報錯:sql異常,delete寫錯了

結果 :數據庫結果顯示插入成功!

沒有進行事務的管理;我們想讓他們都成功才成功,有一個失敗,就都失敗,我們就應該需要事務!

以前我們都需要自己手動管理事務,十分麻煩!

但是Spring給我們提供了事務管理,我們只需要配置即可;

13.2 Spring中的事務管理
Spring在不同的事務管理API之上定義了一個抽象層,使得開發人員不必了解底層的事務管理API就可以使用Spring的事務管理機制。Spring支持編程式事務管理和聲明式的事務管理。

編程式事務管理

將事務管理代碼嵌到業務方法中來控制事務的提交和回滾
缺點:必須在每個事務操作業務邏輯中包含額外的事務管理代碼
聲明式事務管理

一般情況下比編程式事務好用。
將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。
將事務管理作為橫切關注點,通過aop方法模塊化。Spring中通過Spring AOP框架支持聲明式事務管理。
1. 使用Spring管理事務,注意頭文件的約束導入 : tx

xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">



2. JDBC事務

```
  1. 配置好事務管理器后我們需要去配置事務的通知

<!--配置事務通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--給那些方法配置事務-->
    <!--配置事務的傳播特性: new -->
    <tx:attributes>
        <tx:method name="add" propagation="REQUIRED"/>
        <tx:method name="delete" propagation="REQUIRED"/>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="query" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>


spring事務傳播特性:
事務傳播行為就是多個事務方法相互調用時,事務如何在這些方法間傳播。spring支持7種事務傳播行為:

propagation_requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中,這是最常見的選擇。
propagation_supports:支持當前事務,如果沒有當前事務,就以非事務方法執行。
propagation_mandatory:使用當前事務,如果沒有當前事務,就拋出異常。
propagation_required_new:新建事務,如果當前存在事務,把當前事務掛起。
propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
propagation_never:以非事務方式執行操作,如果當前事務存在則拋出異常。
propagation_nested:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與propagation_required類似的操作。
Spring 默認的事務傳播行為是 PROPAGATION_REQUIRED,它適合於絕大多數的情況。

就好比,我們剛才的幾個方法存在調用,所以會被放在一組事務當中!

4. 配置AOP,導入aop的頭文件

    ```
<!--配置事務切入-->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.kuang.mapper.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
  1. 刪掉剛才插入的數據,再次測試!

@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    for (User user : userMapper.selectUser()) {
        System.out.println(user);
    }
}

思考:

為什么需要事務?

如果不配置事務,可能存在數據提交不一致的情況;
如果我們不在Spring中去配置聲明式事務,我們就需要在代碼中手動配置事務!
事務在項目的開發中十分重要,涉及到數據的一致性和完整性問題,不容馬虎!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM