超詳細!!Spring5框架開發筆記

Spring5開發教程

簡介

spring特性

  • sping是輕量級的開源的JavaEE框架

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

  • Sping兩個核心的部分:IOC和AOC

    IOC:控制反轉。把創建對象的過程交給sping進行管理,而不需要自己去new

    AOP:面向切面。不修改源代碼進行功能增強

  • Sping特點

    方便接耦,簡化開發。

    AOP編程支持

    方便程序的測試

    方便集成各種優秀框架

    方便進行事務操作

    降低API開發難度

簡單使用

如果使用maven構建項目,導入spring核心依賴包就可以了:

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
  </dependency>
</dependencies>

隨便創建一個User類,然後創建Spring配置文件,在配置文件配置創建的對象。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置User對象創建-->
    <bean id="user" class="com.atguigu.spring5.User"></bean>
</beans>

class是類路徑

編寫測試代碼

public class TestSpring5 {

    @Test
    public void testAdd(){
        //1 加載spring配置文件
        ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/bean1.xml"); //方式1
              //BeanFactory context = new FileSystemXmlApplicationContext("/src/main/java/bean1.xml");//方式2
        //2 獲取配置創建的對象
        User user = context.getBean("user",User.class);

        System.out.println(user);
        user.add();
    }
}

此時,配置文件已經幫我們創建好了實例,我們直接從配置文件中獲取就行了。

Spring IOC容器

IOC底層原理

什麼是IOC

Inversion Of Control(控制反轉)。把對象創建和對象之間的調用過程,交給Spring管理;使用IOC的目的是為了降低耦合度。

IOC底層主要用到三個技術:xml解析、工廠模式、反射。

  • 工廠模式其實就把把對象的創建交給第三方,降低耦合度(完全沒有耦合度是不能的)

    class UserService{
    	execute(){
    		UserDao dao = new UserFactory.getDao();
    	}
    }
    
    class UserDao{
    	...
    }
    
    class UserFactory{
    	public static UserDao getDao(){
    		return new UserDao();
    	}
    }
    
  • 反射就是java代碼可以動態獲取類的位元組碼文件,從而可以得到類的屬性和方法等。

IOC過程

  1. xml配置文件,配置創建的對象

  2. 有service類,dao類,創建工廠類

    class UserFactory{
    	public static UserDao getDao(){
    		String classValue = class屬性值 //1 xml解析
    		Class class = Class.forName(classValue);//2 通過反射創建對象
    		return (UserDao) class.newInstance();
    	}
    }
    

IOC接口

  • IOC思想基於IOC容器完成,IOC容器底層就是對象工廠。

  • Spring提供IOC容器實現兩種方式:(兩個接口)

    1. BeanFactory:IOC容器最基本實現,是Spring內部的使用接口,不提供開發人員進行使用

      *加載配置文件的時候不會創建對象,在獲取對象(getBean)才去擦黃建對象

    2. ApplicationContext:BeanFactory接口的子接口,提供更多更強大的功能,一般由開發人員使用

      *加載配置文件配置文件的時候就會把配置文件對象進行創建

      一般把耗時耗資源的過程交給服務器啟動的時候去完成,所以ApplicationContext更合適。

IOC操作Bean管理——XML方式

Bean管理是Spring的兩個操作:Spring創建對象;Spring注入屬性(不需要寫set方法,這裡的屬性可以是基本類型,也可以是類對象)。

Bean管理操作有兩種方式:基於xml配置文件方式實現;基於註解方式實現。

基於xml方式

  • 創建對象

    <bean id="user" class="com.atguigu.spring5.User"></bean>
    <!--
    ----------
    bean標籤常用的屬性
    *id:唯一標識
    *class:類全路徑(包類路徑)
    *name:作用和id一樣,可以加特殊符號,目前已棄用
    ----------
    創建對象時候,默認也是執行無參構造方法
    ----------
    -->
    
  • 注入屬性—基本數據類型

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

    <bean id="user" class="com.atguigu.spring5.User">
      <!--  set方法注入屬性:使用property完成屬性注入 -->
      <property name="userName" value="xing"></property>
      <!--
      有參構造方法注入屬性
      <constructor-arg name="userName" value="xing"></constructor-arg>
      -->
    </bean>
    

    使用p名稱空間注入,可簡化基於xml配置方式

    <!--xmlns:p="//www.springframework.org/schema/p"-->
    <bean id="user" class="com.atguigu.spring5.User" p:userName="xing"></bean>
    

    xml注入其他屬性

    <!-- 設置空值 -->
    <property name="userName">
    	<null></null>
    </property>
    
    <!-- 特殊符號如:<<>> -->
    <property name="userName">
    	<value><![CDATA[<<xing>>]]></value>
    </property>
    
  • 注入屬性—外部bean

    創建一個service和dao對象,其中service裏面有一個dao作為屬性

    public class UserService {
        //創建UserDao類型屬性,生成set方法
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public void add(){
            userDao.update();
        }
    }
    

    配置文件

    <!-- service和dao對象的創建-->
    <bean id="userService" class="com.atguigu.spring5.service.UserService">
      <!-- 注入userDao對象
            name屬性值:類裏面屬性名稱
            ref屬性:創建userDao對象bean標籤id值
            -->
      <!-- set 方法注入屬性-->
      <property name="userDao" ref="userDaoImpl"></property>
    </bean>
    <!-- userDao的創建class要指向實現類-->
    <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
    
  • 注入屬性—內部bean

    內部bean可以理解在一個bean裏面嵌套定義另外一個bean,實際應用中,更常用外部bean,因為更加清晰。

    <bean id="userService" class="com.atguigu.spring5.service.UserService">
      <property name="userDao">
      	<bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl">
      </property>
    </bean>
    
  • 注入屬性—級聯賦值

    級聯賦值就是注入外部bean的時候,在外部bean裏面賦值,比如:

    <bean id="userService" class="com.atguigu.spring5.service.UserService">
      <property name="userDao" ref="userDaoImpl"></property>
    </bean>
    
    <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl">
      <property name="userDaoProperty" value="value"></property>
    </bean>
    

    還有一種方式:這種方式要在userService中設置userDaoImpl的set方法,因為userDaoImpl.userDaoProperty要通過set方法來獲取

    <bean id="userService" class="com.atguigu.spring5.service.UserService">
      <property name="userDao" ref="userDaoImpl"></property>
      <property name="userDaoImpl.userDaoProperty" value="value2"></property>
    </bean>
    
  • 注入屬性—集合屬性

    public class Stu {
        private String[] courses;
        private List<String> list;
        private Map<String,String> map;
    
        //對象list
        private List<Course> courseList;
        public void setCourses(String[] courses) {
            this.courses = courses;
        }
    
        public void setList(List<String> list) {
            this.list = list;
        }
    
        public void setMap(Map<String, String> map) {
            this.map = map;
        }
    
        public void setCourseList(List<Course> courseList) {
            this.courseList = courseList;
        }
    }
    
    <bean id="stu" class="com.atguigu.spring5.collection.Stu">
      <!-- 數組屬性的注入 -->
      <property name="courses">
        <array>
          <value>java課程</value>
          <value>數據庫標籤</value>
        </array>
      </property>
      <!-- list類型屬性注入 -->
      <property name="list">
        <list>
          <value>張三</value>
          <value>李四</value>
        </list>
      </property>
      <!-- map類型屬性注入 -->
      <property name="map">
        <map>
          <entry key="Java" value="java"></entry>
        </map>
      </property>
      <!-- 注入對象list類型 -->
      <property name="courseList">
        <list>
          <ref bean="course1"></ref>
          <ref bean="course2"></ref>
        </list>
      </property>
    </bean>
    <bean id="course1" class="com.atguigu.spring5.collection.Course">
      <property name="cname" value="Spring5框架"></property>
    </bean>
    <bean id="course2" class="com.atguigu.spring5.collection.Course">
      <property name="cname" value="Mybatis框架"></property>
    </bean>
    

    還可以把集合注入部分提取出來,方法是在spring配置文件中引入新的名稱空間比如util,模版如下:

    <beans xmlns="//www.springframework.org/schema/beans"
           xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
           xmlns:util="//www.springframework.org/schema/util"
           xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd
                               //www.springframework.org/schema/util //www.springframework.org/schema/utils/spring-utils.xsd">
    
        <!-- 提取list集合類型屬性注入 -->
        <util:list id="bookList">
            <value>java</value>
            <value>c++</value>
        </util:list>
        
        <bean id="book" class="com.atguigu.spring5.collection.Book">
            <property name="list" ref="bookList"></property>
        </bean>
    </beans>
    

FactoryBean

Spring有兩種類型的bean,一種普通bean,另一種工廠bean(FactoryBean)。

普通bean在Spring配置文件中定義什麼類型,返回就是什麼類型;而工廠bean定義的類型和返回的類型可以不一致。

做法:

  • 創建類,讓這個類作為工廠bean,實現接口FactoryBean

  • 實現接口裏面的方法,在實現的方法中定義返回的bean類型

    public class MyBean implements FactoryBean<Course> {
        //定義返回bean
        public Course getObject() throws Exception {
            Course course = new Course();
            course.setCname("java");
            return course;
        }
    
        public Class<?> getObjectType() {
            return null;
        }
    }
    
    <bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
    
    public class TestSpring5 {
        ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/bean1.xml");
        Course course = context.getBean("myBean",Course.class);
    }
    

Bean的作用域

bean的作用域是指:在Spring裏面,設置創建bean實例是單實例還是多實例(每次getBean都獲取新對象),默認情況下是單實例對象。

在bean標籤裏面scope屬性用來設置單實例還是多實例,默認值singletonprototype表示多實例。

兩個值的區別:singleton時候,加載spring配置文件時候就會創建單實例對象,而prototype在調用getBean方法時候才會去創建。

Bean的生命周期

  1. 通過構造器創建bean實例(無參數構造)
  2. 為bean的屬性設置值和對其他bean引用(調用set方法)
  3. *把bean實例傳遞bean後置處理器(實現BeanPostProcessor的類對象)的方法
  4. 調用bean的初始化方法(需要配置初始化的方法 bean標籤的init-method屬性
  5. *把bean實例傳遞bean後置處理器的方法
  6. bean可以使用了(對象獲取到了)
  7. 當容器關閉的時候(context.close()),調用bean的銷毀方法(需要配置銷毀的方法 bean標籤的destroy-method屬性

xml自動裝配

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

<!--
     bean標籤屬性autowire,配置自動裝配
    autowire屬性常用兩個值:
        byName:根據屬性名稱注入,注入bean的id值和類屬性名稱一樣
        byType:根據屬性類型注入,相同類型的bean不能有多個
    -->
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName"></bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>

基於xml的自動裝配很少用到,基本都是使用註解

引入外部屬性文件

以配置數據庫連接池為例:

  1. 創建外部屬性文件.properties格式文件,寫數據庫信息

    prop.driverClass=com.mysql.jdbc.Driver
    prop.url=jdbc:mysql://localhost:3306/userDb
    prop.userName=root
    prop.passWord=root
    #等號左邊可以隨便寫,但建議不要寫某一個單詞,容易衝突
    
  2. 把外部properties屬性文件引入到spring配置文件中

    首先引入context名稱空間,先後在spring配置文件中使用標籤引入外部屬性文件

    <!-- 引入外部屬性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--配置連接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <property name="driverClassName" value="${prop.driverClass}"></property>
      ...
    </bean>
    

IOC操作Bean管理——註解方式

註解可以作用在類、屬性、方法上面,可以簡化我們的xml配置。

Spring針對Bean管理中創建對象提供的註解四種,他們都作用在類上面:

  • @Component
  • @Service
  • @Controller
  • @Repository

*** 這四個註解功能是一樣的,都可以用來創建bean實例,只不過在開發中為了清晰的表明這個bean用在哪一個層上面**

使用註解創建對象

  1. 引入AOP依賴

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.8.RELEASE</version>
    </dependency>
    
  2. 開啟組件掃描

    指定要掃描的包,這個包中的類有註解就回去創建對象。

    首先引入context名稱空間

        <!--開啟組件掃描
            1 如果掃描多個包,多個包使用逗號隔開
            2 也可以掃描包的上層目錄(推薦)
        -->
        <context:component-scan base-package="com.atguigu.spring5"></context:component-scan>
    
  3. 創建類,在類上面添加創建對象註解即可

    //value屬性可以省略不寫,默認值是類名稱首字母小寫
    @Component(value = "userService") //<bean id="userService" class="..."></bean>
    public class UserService{
      //...
    }
    

開啟組件掃描細節配置

這個表示只掃描包裏面帶Controller註解的類

<!--示例1
        use-default-filters="false" 表示現在不使用默認的filter(所有類都掃描),自己配置filter
        context:include-filter,設置掃描哪些內容
 -->
<context:component-scan base-package="com.atguigu.spring5" use-default-filters="false">
  <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--示例2
        context:exclude-filters:設置哪些內容不進行掃描
    -->
<context:component-scan base-package="com.atguigu.spring5">
  <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

基於註解方式實現屬性注入

  • @AutoWired:根據屬性類型進行自動裝配(byType),不需要添加set方法,如果接口有多個實現類的話,@AutoWired就不知道找哪個了,這個時候就要配合@Qualifier根據名稱進行自動裝配了

  • @Qualifier:根據屬性名稱進行自動裝配(byName),要和@AutoWired一起使用

    @AutoWired
    @Qualifier(value = "userDaoImpl")
    private UserDao userDao;
    
  • @Resource:可以根據類型注入,也可以根據名稱注入。這個註解在javax.annotation.Resource包中,不是Spring本身提供的,因此官方建議使用前兩者。

  • @Value:注入普通類型屬性

    @Value(value="abc")
    private String name;
    

完全註解開發

  1. 創建配置類,替代xml配置文件

    @Configuration //Spring才能識別為配置類,替代xml文件
    @ComponentScan(basePackages = {"com.atguigu.spring5"})
    public class SpringConfig {
        
    }
    
  2. 加載配置類

    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    

Spring AOP

概念

AOP:Aspect Oriented Programming,面向切面編程。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。

通俗點說就是:在不修改源代碼的情況下增強類的功能。

底層原理

AOP底層使用動態代理。有兩種情況動態代理:

  • 有接口情況:使用JDK動態代理,創建接口實現類代理對象來增強接口功能
  • 和沒有接口情況:使用CGLIB動態代理,創建當前類子類的代理對象來增強類功能

使用JDK動態代理:

  1. 使用java.lang.reflect.Proxy類裏面的newProxyInstance方法創建代理對象,首先我們創建接口以及實現類

    static Object newProxyInstance(ClassLoader loader,class<?>[] interfaces,InvocationHandler h);
    //返回指定接口的代理類實現,該接口將方法調用分派給指定的調用處理程序
    //第一個參數:類加載器
    //第二個參數:增強類所實現的接口,可以有多個
    //第三個參數:實現InvocationHandler接口的一個對象,這個對象裏面通過實現invoke()方法來寫增強的邏輯
    
    public interface UserDao {
        public int add(int a,int b);
        public String update(String id);
    }
    
    //-------
    public class UserDaoImpl implements UserDao{
        public int add(int a, int b) {
            System.out.println("add方法執行了。。。");
            return a+b;
        }
    
        public String update(String id) {
            System.out.println("update方法執行了。。。");
            return id;
        }
    }
    
  2. 使用Proxy類創建接口實現類代理對象

    public class JDKProxy {
        public static void main(String[] args) {
            //創建接口實現類代理對象
            Class[] interfaces = {UserDao.class};
    //        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
    //            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //                return null;
    //            }
    //        });
            UserDaoImpl userDao = new UserDaoImpl();
            UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
            int result = dao.add(1,2);
            System.out.println("result:"+result);
        }
    }
    
    class UserDaoProxy implements InvocationHandler{
        //1 創建的是誰的代理對象,就把誰傳進來
        //有參構造傳遞
        private Object obj;
        public UserDaoProxy(Object obj){
            this.obj = obj;
        }
    
        //寫增強邏輯
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //方法之前
            System.out.println("方法之前執行。。。"+method.getName()+"傳遞的參數:"+ Arrays.toString(args));
    
            //被增強的方法執行
            //這裡也可以判斷執行的方法去做不同的處理
            Object res = method.invoke(obj,args);
            
            //方法執行之後
            System.out.println("方法之後執行..."+obj);
            return null;
        }
    }
    

AOP術語

  • 連接點:在一個類中,哪些方法可以被增強,這些方法稱為連接點
  • 切入點 :實際被真正增強的方法
  • 通知(增強):實際增強的邏輯部分。通知有多種類型:前置通知、後置通知、環繞通知、異常通知、最終通知
  • 切面:把通知應用到切入點的過程,指的是一個動作

Spring框架中一般都是基於AspectJ實現AOP操作,需要引入AOP相關依賴

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <!--AspectJ 開始-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.0</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--AspectJ 結束-->

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

AOP操作

切入點表達式作用:知道對哪個類裏面的哪個方法進行增強

語法結構

execution([權限修飾符][返回類型][類全路徑][方法名稱][參數列表])
  1. 對com.atguigu.dao.BookDao類裏面的add方法進行增強

    execution(* com.atuguigu.dao.BookDao.add(..))
    

    *表示所有修飾符,返回類型可以省略

  2. 對com.atguigu.dao.BookDao類裏面的所有方法增強

    execution(* com.atuguigu.dao.BookDao.*(..))
    
  3. 對com.atguigu.dao包裏面所有類,類裏面的所有方法進行增強

    execution(* com.atuguigu.dao.*.*(..))
    

AspectJ註解

  1. 創建類,在類裏面定義方法

    //被增強類
    @Component
    public class User {
        public void add(){
            System.out.println("add....");
        }
    }
    
  2. 創建增強類(編寫增強邏輯)

    在增強類裏面創建方法,讓不同的的方法代表不同的通知類型

    //增強類
    @Component
    @Aspect //生成代理對象
    public class UserProxy {
        //Before註解表示作為前置通知
        @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
        public void before(){
            System.out.println("before....");
        }
        
        @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
        public void after(){
            System.out.println("after...");
        }
    	 //方法返回後執行,比after早,有異常不執行
        @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
        public void afterReturning(){
            System.out.println("afterReturning...");
        }
    	 //有異常執行
        @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
        public void afterThrowing(){
            System.out.println("afterThrowing...");
        }
    
        //環繞通知:在方法之前和之後都通知
        @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
            System.out.println("環繞之前...");
    
            //被增強的方法執行
            proceedingJoinPoint.proceed();
    
            System.out.println("環繞之後...");
        }
    }
    
  3. 進行通知配置

    在spring配置文件中,開啟註解掃描,開啟生成代理的對象

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="//www.springframework.org/schema/beans"
           xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
           xmlns:context="//www.springframework.org/schema/context"
           xmlns:aop="//www.springframework.org/schema/aop"
           xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd
                               //www.springframework.org/schema/context  //www.springframework.org/schema/context/spring-context.xsd
                               //www.springframework.org/schema/aop  //www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--引入context/aop名稱空間-->
        <!--開啟註解掃描-->
        <context:component-scan base-package="com.atguigu.spring5"></context:component-scan>
          <!--開啟AspectJ生成代理對象-->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
        
    </beans>
    
  4. 配置不同類型的通知

    在增強類裏面,在作為通知方法上面添加通知類型註解,使用切入點表達式配置。

  5. 測試

        @Test
        public void testAopAnno(){
            ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/bean1.xml");
            User user = context.getBean("user", User.class);
            user.add();
        }
    /*
    output:
    
    環繞之前...
    before....
    add....
    afterReturning...
    after...
    環繞之後...
    
    */
    
  6. 相同的切入點抽取

        //相同切入點抽取
        @Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
        public void pointdemo(){
    
        }
    
        //Before註解表示作為前置通知
        @Before(value = "pointdemo()")
        public void before(){
            System.out.println("before....");
        }
    
  7. 有多個增強類對同一個方法進行增強,設置增強類的優先級。

    在增強類上面添加註解@Order(數字),數字值越小優先級越高

AspectJ配置文件

AspectJ也可以通過配置文件使用

JdbcTemplate

概念

Spring框架對JDBC進行封裝,使用JdbcTemplate可以很方便的實現對數據庫的操作。

準備工作

  1. 引入依賴

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.23</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>5.2.6.RELEASE</version>
            </dependency>
    
  2. 在spring配置文件配置數據庫連接池

  3. 配置JdbcTemplate對象,注入DataSource

        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 注入dataSource -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
  4. 創建dao類,注入jdbcTemplate對象;創建service類,注入dao對象

CRUD操作

  1. 增刪改操作使用的是update(String sql, Object... args)函數

    @Repository
    public class BookDaoImpl implements BookDao{
      //注入jdbcTemplate
      private JdbcTemplate jdbcTemplate;
      
      //添加方法
      @Override
      public void add(Book book){
        //1 創建sql語句
        String sql = "insert into t_book values(?,?,?)";
        //2 調用方法實現
        Object[] args = {book.getUserId(),book.getUsername(),book.getUstatus};
        int update = jdbcTemplate.update(sql,args);
      }
    }
    
  2. 查詢返回某一個值

    查詢操作使用的是queryForObject(String sql, Class<T> requiredType)方法,第二個參數表示查詢操作的返回類型。

    String sql = "select count(*) from t_book";
    Integer count = jdbcTemplate.queryForObject(sql,Integer.class);
    
  3. 查詢返回對象

    queryForObject(String sql,RowMapper<T> rowMapper,Object... args)

    query(String sql,RowMapper<T> rowMapper,Object... args):返回對象列表

    第二個參數rowMapper是接口,針對返回不同類型數據,使用這個接口裏面實現類完成數據封裝

    String sql = "select * from t_book where user_id=?";
    //調用方法
    Book book = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Book>(Book.class),id);
    
  4. 批量操作

    batchUpdate(String sql,List<Object[]> batchArgs)

    public void batchAdd(List<Object[]> batchArgs){
      String sql = "insert into t_book values(?,?,?)";
      int[] ints = jdbcTemplate.batchUpdate(sql,batchArgs);
    }
    

Spring聲明式事務

Sping事務管理介紹

  1. 事務添加到JavaEE三層結構裏面Service層(業務邏輯層)

  2. 在Spring進行事務管理操作有兩種方式:編程式和聲明式

    編程式

    public void accountMoney(){
      try{
        //1 開啟事務
        
        //2 進行業務操作
        userDao.reduceMoney();
        
        userDao.addMoney();
        //3 沒有發生異常,提交事務
      }catch(Exception e){
        //4 出現異常 事務回滾
      }
    }
    
  3. 聲明式事務管理有基於註解方式,也有基於xml配置文件方式

  4. 在Spring進行聲明式事務管理,底層使用AOP原理

  5. Spring事務管理API提供一個接口,代表事務管理器,這個接口針對不同的框架提供不同的實現類

註解方式

  1. 在配置文件中配置事務管理器,並開啟事務註解

    <!-- 創建事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <!-- 注入數據源 -->
      <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 開啟事務註解 引入名稱空間tx-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    
  2. 在service類上面添加事務註解@Transactional

    這個註解可以添加到類上面,也可以添加上方法上面,如果添加上類上面表示這個類裏面所有的方法都添加事務。

  3. 這個註解裏面可以配置事務相關的參數

    propagation:事務傳播行為。多事務方法(對數據庫表中的數據發生變化的操作)進行互相調用,這個過程中事務是如何進行管理的。Spring定義了7種傳播行為

    截屏2021-04-24 下午1.33.46

    截屏2021-04-24 下午1.48.47

    isolation:事務的隔離級別,默認是可重複讀

    截屏2021-04-24 下午1.57.02

    timeout:超時時間。事務需要在一定時間內進行提交,如果不提交進行回滾。默認值-1(沒有超時),單位秒

    readOnly:是否只讀。默認false,true表示只能做查詢操作

    rolllbackFor:回滾。設置出現哪些異常進行回滾

    noRollbackFor:不回滾。設置出現哪些異常不進行回滾

xml方式

  1. 配置事務管理器
  2. 配置通知
  3. 配置切入點和切面
<!--配置通知-->
<tx:advice id="txadvice">
	<!--配置事務參數-->
  <tx:attributes>
  	<!--指定哪種規則的方法上面添加事務-->
    <tx:method name="accountMoney" propagation="REQUIRED"></tx:method>
  </tx:attributes>
</tx:advice>

<aop:config>
	<!-- 配置切入-->
  <aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
  <!-- 配置切面-->
  <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

完全註解開發

  1. 創建配置類替代xml配置文件

    @Configuration
    @ComponentScan(basePackages="com.atguigu")
    @EnableTransactionManagement //開啟事務
    public class TxConfig{
      //創建數據庫連接池
      @Bean
      public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.SetDriverClassName("");
        dataSource.SetUrl("");
        dataSource.SetUsername("");
        dataSource.SetPassword("");
        return dataSource;
      }
      
      //創建JdbcTemplate對象
      @Bean
      public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        //到ioc容器中根據類型找到dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcemplate;
      }
      
      //創建事務管理器
      @Bean
      public DataSourceTransactionManager getDataSourceTransationManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransationManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
      }
    }
    

Spring5新功能

整個Spring5框架的代碼基於Java8,運行時兼容JDK9,許多不建議使用的類和方法在代碼庫中刪除。

整合日誌框架

Spring5自帶類通用的日誌封裝,已經移除了Log4jConfigListener,官方建議使用Log4j2。

Nullable註解和函數式註冊對象

@Nullable註解可以使用在方法上面,屬性上面,參數上面,表示方法返回可以為空,屬性值可以為空,參數值可以為空。

Spring5核心容器支持函數時風格GenericApplicationContext,通過lambda表達式註冊對象

public void test(){
	//1 創建GenericApplicationContext對象
  GenericApplicationConntext context = new GenericApplicationContext();
  //2 調用context的方法對象註冊
  context.refresh();
  context.registerBean(User.class, ()-> new User());
  //context.registerBean("user1",User.class,()->new User());
  //3 獲取在spring註冊的對象
  User user = context.getBean("com.atguigu.spring5.test.User");
  //User user = context.getBean("user1");
}

支持整合JUnit5

SpringWebFlux

截屏2021-04-24 下午3.41.27

簡介

WebFlux是Spring5添加新的模塊,用於web開發,功能和SpringMVC類似,WebFlux使用當前一種比較流行的響應式出現的框架。

傳統的web框架比如SpringMVC,這些基於Servlet容器,WebFlux是一種異步非阻塞的框架,異步非阻塞的框架在Servlet3.1以後才支持,核心是基於Reactor的相關API實現。

同步異步針對調用者:調用者發送請求,如果等着對方回應之後才去做其他事情就是同步,如果發送請求之後不等着對方回應就去做其他事情就是異步

阻塞和非阻塞針對被調用者:被調用者收到請求之後,做完請求任務之後才給出反饋就是阻塞,收到請求之後馬上給出反饋然後再去做其他事情就是非阻塞

WebFlux特點:

  • 非阻塞:在有限資源下,提高系統的吞吐量和伸縮性,以Reactor為基礎實現響應式編程
  • 函數式編程:Spring5框架基於java8,WebFlux使用java8函數式編程方式實現路由請求

WebFlux VS SpringMVC

截屏2021-04-24 下午4.14.51

  • 都可以使用註解方式,都運行在Tomcat等容器中
  • SpringMVC採用命令式編程,WebFlux採用異步響應式編程

響應式編程

響應式編程是一種面向數據流和變化傳播的編程範式,這意味着可以在編程語言中很方便的表達靜態或動態的數據流,而相關的計算模型會自動將變化的值通過數據流進行傳播。

響應式編程使用觀察者模式,在java8及之前的版本提供的觀察者模式兩個類Observer和Observerable。

在java8之後被Flow類取代

Reactor實現

響應式編程操作中,Reactor是滿足Reactive規範框架,其有兩個核心類Mono和Flux,這兩個類實現接口Publisher,提供豐富操作符。Flux對象實現發佈者,返回N個元素;Mono實現發佈者返回0或1個元素。

Flux和Mono都是數據流的發佈者,使用Flux和Mono都可以發出三種數據信號:元素值、錯誤信號、完成信號。後兩個代表終止信號,用於告訴訂閱者數據流結束了,錯誤信號在終止數據流的同時把錯誤信息傳遞給訂閱者。

  1. 引入依賴

    <dependency>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-core</artifactId>
      <version>3.3.5.RELEASE</version>
    </dependency>
    
  2. public static void main(String[] args) {
      //just方法直接聲明
      Flux.just(1,2,3,4);
      Mono.just(1);
    
      //其他方法
      Integer[] array = {1,2,3,4};
      Flux.fromArray(array);
    
      List<Integer> list = Arrays.asList(array);
      Flux.fromIterable(list);
    
    }
    
  3. 錯誤信號和完成信號都是終止,不能共存;如果沒有發送任何元素值,而是直接發送錯誤或完成信號表示空數據流;如果沒有錯誤信號,沒有完成信號,表示無限數據流。

  4. 調用just或者其他方法只是聲明數據流,數據流並沒有發出,只有進行訂閱之後才會觸發數據流。

  5. 對數據流進行一道道操作,成為操作符,比如工廠流水線

    map:元素映射為新元素

    截屏2021-04-24 下午6.29.37

    flatMap:把每個元素轉換成流,把轉換之後的多個流合併成大的流

    截屏2021-04-24 下午6.31.39

WebFlux執行流程和核心API

WebFlux基於Reactor,默認容器是Netty,Netty是高性能的NIO框架,異步非阻塞的框架。

SpringWebFlux執行過程和SpringMVC相似:

  • SpringWebFlux核心控制器DispatchHandler,負責請求的處理,實現WebHandler接口
  • HandlerMapping:請求查詢到處理的方法
  • HandlerAdapter:真正負責請求處理
  • HandlerResultHandler:響應結果處理

SpringWebFlux實現函數式編程的兩個接口:RouterFunction(路由處理)和HandlerFunction(處理函數)

基於註解編程模型

使用註解編程模型方式,和之前SpringMVC使用相似,只需要把相關依賴配置到項目中,SpringBoot自動配置相關運行容器,默認情況下使用Netty服務器。

  1. 引入webflux相關依賴,創建一個SpringBoot工程

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</artifactId>
                <version>2.3.5.RELEASE</version>
            </dependency>
    
  2. 創建實體類User,以及service類

    @Service
    public class UserServiceImpl implements UserService {
    
        //創建map集合存儲數據
        private final Map<Integer,User> users = new HashMap<Integer, User>();
    
        public UserServiceImpl(){
            this.users.put(1,new User("lucy","boy",20));
            this.users.put(2,new User("mary","girl",20));
            this.users.put(3,new User("jack","boy",20));
        }
    
        public Mono<User> getUserById(int id) {
            return Mono.justOrEmpty(this.users.get(id));
        }
    
        public Flux<User> getAllUser() {
            return Flux.fromIterable(this.users.values());
        }
    
        public Mono<Void> saveUserInfo(Mono<User> userMono) {
            return userMono.doOnNext(person ->{
                //向map集合裏面放值
                int id = users.size();
                users.put(id,person);
            }).thenEmpty(Mono.empty());//Mone.empty()表示數據流結束
        }
    }
    
  3. 創建controller

    @RestController
    public class UserController {
        @Autowired
        private UserService userService;
    
        @GetMapping("/user/{id}")
        public Mono<User> getUserById(@PathVariable int id){
            return userService.getUserById(id);
        }
    
        @GetMapping("/user")
        public Flux<User> getUsers(){
            return userService.getAllUser();
        }
    
        @PostMapping("/saveuser")
        public Mono<Void> saveUser(@RequestBody User user){
            Mono<User> userMono = Mono.just(user);
            return userService.saveUserInfo(userMono);
        }
    }
    

雖然形式上和SpringMVC方式差不多,但是底層不一樣了。前者基於SpringMVC+Servelet+Tomcat,後者基於SpringWebFlux+Reactor+Netty。

基於函數式編程模型

在使用函數式編程模型操作時,需要自己初始化服務器。

基於函數式編程模型的時候,有兩個核心接口:RouterFunction(實現路由功能,請求轉發給對應的Handler)和HandlerFunction(處理請求生成響應的函數)。核心任務定義兩個函數式接口的實現並且啟動需要的服務器。

SpringWebFlux請求和響應不再是ServletRequest和ServeltResponse,而是ServerRequest和ServerRespone。

  1. 創建Handler

    public class UserHandler {
    
        private final UserService userService;
        public UserHandler(UserService userService){
            this.userService = userService;
        }
    
        public Mono<ServerResponse> getUserById(ServerRequest serverRequest){
            int userID = Integer.valueOf(serverRequest.pathVariable("id"));
            //空值處理
            Mono<ServerResponse> notFount = ServerResponse.notFound().build();
            Mono<User> userMono = this.userService.getUserById(userID);
            //把userMono進行轉換返回
            //使用Reactor操作符flatMap
            return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                    .body(fromObject(person)))
                    .switchIfEmpty(notFount);
        }
    
        public Mono<ServerResponse> getAllUsers(ServerRequest serverRequest){
            //調用service得到結果
            Flux<User> users = this.userService.getAllUser();
            return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
        }
    
        public Mono<ServerResponse> saveUser(ServerRequest serverRequest){
            //得到user對象
            Mono<User> userMono = serverRequest.bodyToMono(User.class);
            return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
        }
    }
    
  2. 初始化服務器,編寫Router和adapter

    image-20210425103057948

    public class Server {
        public static void main(String[] args) throws Exception {
            Server server = new Server();
            server.createReactorService();
            System.out.println("enter to exit");
            System.in.read();
        }
    
        //1 創建Router路由
        public RouterFunction<ServerResponse> routingFunction(){
            UserService userService = new UserServiceImpl();
            UserHandler userHandler = new UserHandler(userService);
    
            return RouterFunctions.route(
                    GET("/users/{id}").and(accept(MediaType.APPLICATION_JSON)),userHandler::getUserById
            ).andRoute(
                    GET("/users").and(accept(MediaType.APPLICATION_JSON)),userHandler::getAllUsers
            );
    
        }
    
        // 2 創建服務器完成適配
        public void createReactorService(){
            //路由和Handler適配
            RouterFunction<ServerResponse> route = routingFunction();
            HttpHandler httpHandler = toHttpHandler(route);
            ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
    
            //創建服務器
            HttpServer httpServer = HttpServer.create();
            httpServer.handle(adapter).bindNow();
        }
    }
    
    
Tags: