Spring框架學習重點概要筆記(1.0版本)1.0沒整理事務

Spring 框架兩大核心機制(IoC、AOP)

  • IoC(控制反轉)/ DI(依賴注入)
  • AOP(面向切面編程)

Spring 是一個企業級開發框架,是軟件設計層面的框架,優勢在於可以將應用程序進行分層,開發者可以自主選擇組件。

MVC:Struts2、Spring MVC

ORMapping:Hibernate、MyBatis、Spring Data

如何使用 IoC

  • 創建 Maven 工程,pom.xml 添加依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.southwind</groupId>
    <artifactId>aispringioc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.11.RELEASE</version>
        </dependency>
    </dependencies>

</project>
  • 創建實體類 Student
package com.southwind.entity;

import lombok.Data;

@Data
public class Student {
    private long id;
    private String name;
    private int age;
}
  • 傳統的開發方式,手動 new Student
Student student = new Student();
student.setId(1L);
student.setName("六道");
student.setAge(22);
System.out.println(student);
  • 通過 IoC 創建對象,在配置文件中添加需要管理的對象,XML 格式的配置文件,文件名可以自定義。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
">

    <bean id="student" class="com.southwind.entity.Student">
        <property name="id" value="1"></property>
        <property name="name" value="六道"></property>
        <property name="age" value="22"></property>
    </bean>

</beans>
  • 從 IoC 中獲取對象,通過 id 獲取。
//加載配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);

配置文件

  • 通過配置 bean 標籤來完成對象的管理。

    • id:對象名。
    • class:對象的模版類(所有交給 IoC 容器來管理的類必須有無參構造函數,因爲 Spring 底層是通過反射機制來創建對象,調用的是無參構造)
  • 對象的成員變量通過 property 標籤完成賦值。

    • name:成員變量名。
    • value:成員變量值(基本數據類型,String 可以直接賦值,如果是其他引用類型,不能通過 value 賦值)
    • ref:將 IoC 中的另外一個 bean 賦給當前的成員變量(DI)
    <bean id="student" class="com.southwind.entity.Student">
        <property name="id" value="1"></property>
        <property name="name" value="六道"></property>
        <property name="age" value="22"></property>
        <property name="address" ref="address"></property>
    </bean>
    
    <bean id="address" class="com.southwind.entity.Address">
        <property name="id" value="1"></property>
        <property name="name" value="神諭科技"></property>
    </bean>
    

IoC 底層原理

  • 讀取配置文件,解析 XML。
  • 通過反射機制實例化配置文件中所配置所有的 bean。
package com.southwind.ioc;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ClassPathXmlApplicationContext implements ApplicationContext {
    private Map<String,Object> ioc = new HashMap<String, Object>();
    public ClassPathXmlApplicationContext(String path){
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read("./src/main/resources/"+path);
            Element root = document.getRootElement();
            Iterator<Element> iterator = root.elementIterator();
            while(iterator.hasNext()){
                Element element = iterator.next();
                String id = element.attributeValue("id");
                String className = element.attributeValue("class");
                //通過反射機制創建對象
                Class clazz = Class.forName(className);
                //獲取無參構造函數,創建目標對象
                Constructor constructor = clazz.getConstructor();
                Object object = constructor.newInstance();
                //給目標對象賦值
                Iterator<Element> beanIter = element.elementIterator();
                while(beanIter.hasNext()){
                    Element property = beanIter.next();
                    String name = property.attributeValue("name");
                    String valueStr = property.attributeValue("value");
                    String ref = property.attributeValue("ref");
                    if(ref == null){
                        String methodName = "set"+name.substring(0,1).toUpperCase()+name.substring(1);
                        Field field = clazz.getDeclaredField(name);
                        Method method = clazz.getDeclaredMethod(methodName,field.getType());
                        //根據成員變量的數據類型將 value 進行轉換
                        Object value = null;
                        if(field.getType().getName() == "long"){
                            value = Long.parseLong(valueStr);
                        }
                        if(field.getType().getName() == "java.lang.String"){
                            value = valueStr;
                        }
                        if(field.getType().getName() == "int"){
                            value = Integer.parseInt(valueStr);
                        }
                        method.invoke(object,value);
                    }
                    ioc.put(id,object);
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e){
            e.printStackTrace();
        } catch (NoSuchMethodException e){
            e.printStackTrace();
        } catch (InstantiationException e){
            e.printStackTrace();
        } catch (IllegalAccessException e){
            e.printStackTrace();
        } catch (InvocationTargetException e){
            e.printStackTrace();
        } catch (NoSuchFieldException e){
            e.printStackTrace();
        }
    }

    public Object getBean(String id) {
        return ioc.get(id);
    }
}

通過運行時類獲取 bean

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean(Student.class);//這裏沒有通過id獲取bean
System.out.println(student);

在這裏插入圖片描述
這種方式存在一個問題,配置文件中一個數據類型的對象只能有一個實例,否則會拋出異常,因爲沒有唯一的 bean。

通過有參構造創建 bean

在這裏插入圖片描述

  • 在實體類中創建對應的有參構造函數。
  • 配置文件
<bean id="student3" class="com.southwind.entity.Student">
    <constructor-arg name="id" value="3"></constructor-arg>
    <constructor-arg name="name" value="小明"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="address" ref="address"></constructor-arg>
</bean>
<bean id="student3" class="com.southwind.entity.Student">
    <constructor-arg index="0" value="3"></constructor-arg>
    <constructor-arg index="2" value="18"></constructor-arg>
    <constructor-arg index="1" value="小明"></constructor-arg>
    <constructor-arg index="3" ref="address"></constructor-arg>
</bean>

給 bean 注入集合

<bean id="student" class="com.southwind.entity.Student">
    <property name="id" value="2"></property>
    <property name="name" value="李四"></property>
    <property name="age" value="33"></property>
    <property name="addresses">
        <list>
            <ref bean="address"></ref>
            <ref bean="address2"></ref>
        </list>
    </property>
</bean>

<bean id="address" class="com.southwind.entity.Address">
    <property name="id" value="1"></property>
    <property name="name" value="科技路"></property>
</bean>

<bean id="address2" class="com.southwind.entity.Address">
    <property name="id" value="2"></property>
    <property name="name" value="高新區"></property>
</bean>

scope 作用域

Spring 管理的 bean 是根據 scope 來生成的,表示 bean 的作用域,共4種,默認值是 singleton。

  • singleton:單例,表示通過 IoC 容器獲取的 bean 是唯一的。
  • prototype:原型,表示通過 IoC 容器獲取的 bean 是不同的。
  • request:請求,表示在一次 HTTP 請求內有效。
  • session:回話,表示在一個用戶會話內有效。

request 和 session 只適用於 Web 項目,大多數情況下,使用單例和原型較多。

prototype 模式當業務代碼獲取 IoC 容器中的 bean 時,Spring 纔去調用無參構造創建對應的 bean。//加載配置文件不會創建,延遲加載

singleton 模式無論業務代碼是否獲取 IoC 容器中的 bean,Spring 在加載 spring.xml 時就會創建 bean。//及時加載

Spring 的繼承

與 Java 的繼承不同,Java 是類層面的繼承,子類可以繼承父類的內部結構信息;Spring 是對象層面的繼承,子對象可以繼承父對象的屬性值。

<bean id="student2" class="com.southwind.entity.Student">
    <property name="id" value="1"></property>
    <property name="name" value="張三"></property>
    <property name="age" value="22"></property>
    <property name="addresses">
        <list>
            <ref bean="address"></ref>
            <ref bean="address2"></ref>
        </list>
    </property>
</bean>

<bean id="address" class="com.southwind.entity.Address">
    <property name="id" value="1"></property>
    <property name="name" value="科技路"></property>
</bean>

<bean id="address2" class="com.southwind.entity.Address">
    <property name="id" value="2"></property>
    <property name="name" value="高新區"></property>
</bean>

<bean id="stu" class="com.southwind.entity.Student" parent="student2">
    <property name="name" value="李四"></property>
</bean>

Spring 的繼承關注點在於具體的對象,而不在於類,即不同的兩個類的實例化對象可以完成繼承,前提是子對象必須包含父對象的所有屬性,同時可以在此基礎上添加其他的屬性。

Spring 的依賴(如果要指定加載順序可以同依賴)

與繼承類似,依賴也是描述 bean 和 bean 之間的一種關係,配置依賴之後,被依賴的 bean 一定先創建,再創建依賴的 bean,A 依賴於 B,先創建 B,再創建 A。

<?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-3.2.xsd
                           ">

    <bean id="student" class="com.southwind.entity.Student" depends-on="user"></bean>

    <bean id="user" class="com.southwind.entity.User"></bean>

</beans>

Spring 的 p 命名空間 簡化bean的配置

p 命名空間是對 IoC / DI 的簡化操作,使用 p 命名空間可以更加方便的完成 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:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
">

    <bean id="student" class="com.southwind.entity.Student" p:id="1" p:name="張三" p:age="22" p:address-ref="address"></bean>

    <bean id="address" class="com.southwind.entity.Address" p:id="2" p:name="科技路"></bean>

</beans>

Spring 的工廠方法

IoC 通過工廠模式創建 bean 的方式有兩種:

  • 靜態工廠方法
  • 實例工廠方法

靜態工廠方法

package com.southwind.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
    private long id;
    private String name;
}
package com.southwind.factory;

import com.southwind.entity.Car;

import java.util.HashMap;
import java.util.Map;

public class StaticCarFactory {
    private static Map<Long, Car> carMap;
    static{//類加載static就被執行
    //生產好汽車,提供一個get的獲取方法
        carMap = new HashMap<Long, Car>();
        carMap.put(1L,new Car(1L,"寶馬"));
        carMap.put(2L,new Car(2L,"奔馳"));
    }
    //不需要實例化工廠,直接調用方法獲取對象
    public static Car getCar(long id){
        return carMap.get(id);
    }
}
<!-- 配置靜態工廠創建 Car 讓ioc管理這個工廠-->
<bean id="car" class="com.southwind.factory.StaticCarFactory" factory-method="getCar">
    <constructor-arg value="2"></constructor-arg>
</bean>

實例工廠方法

package com.southwind.factory;

import com.southwind.entity.Car;

import java.util.HashMap;
import java.util.Map;

public class InstanceCarFactory {
    private Map<Long, Car> carMap;
    public InstanceCarFactory(){
        carMap = new HashMap<Long, Car>();
        carMap.put(1L,new Car(1L,"寶馬"));
        carMap.put(2L,new Car(2L,"奔馳"));
    }

    public Car getCar(long id){
        return carMap.get(id);
    }
}
<!-- 配置實例工廠 bean -->
<bean id="carFactory" class="com.southwind.factory.InstanceCarFactory"></bean>

<!-- 賠償實例工廠創建 Car -->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
    <constructor-arg value="1"></constructor-arg>
</bean>

IoC 自動裝載(Autowire)

IoC 負責創建對象,DI 負責完成對象的依賴注入,通過配置 property 標籤的 ref 屬性來完成,同時 Spring 提供了另外一種更加簡便的依賴注入方式:自動裝載,不需要手動配置 property,IoC 容器會自動選擇 bean 完成注入。

自動裝載有兩種方式:

  • byName:通過屬性名自動裝載
  • byType:通過屬性的數據類型自動裝載

byName

<bean id="cars" class="com.southwind.entity.Car">
    <property name="id" value="1"></property>
    <property name="name" value="寶馬"></property>
</bean>

<bean id="person" class="com.southwind.entity.Person" autowire="byName">
    <property name="id" value="11"></property>
    <property name="name" value="張三"></property>
</bean>

byType

<bean id="car" class="com.southwind.entity.Car">
    <property name="id" value="2"></property>
    <property name="name" value="奔馳"></property>
</bean>

<bean id="person" class="com.southwind.entity.Person" autowire="byType">
    <property name="id" value="11"></property>
    <property name="name" value="張三"></property>
</bean>

byType 需要注意,如果同時存在兩個及以上的符合條件的 bean 時,自動裝載會拋出異常。

AOP

AOP:Aspect Oriented Programming 面向切面編程。

AOP 的優點:

  • 降低模塊之間的耦合度。
  • 使系統更容易擴展。
  • 更好的代碼複用。
  • 非業務代碼更加集中,不分散,便於統一管理。
  • 業務代碼更加簡潔存粹,不參雜其他代碼的影響。

AOP 是對面向對象編程的一個補充,在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面編程。將不同方法的同一個位置抽象成一個切面對象,對該切面對象進行編程就是 AOP。

如何使用?

  • 創建 Maven 工程,pom.xml 添加
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.11.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.0.11.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.0.11.RELEASE</version>
    </dependency>
</dependencies>
  • 創建一個計算器接口 Cal,定義4個方法。
package com.southwind.utils;

public interface Cal {
    public int add(int num1,int num2);
    public int sub(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}
  • 創建接口的實現類 CalImpl。
package com.southwind.utils.impl;

import com.southwind.utils.Cal;

public class CalImpl implements Cal {
    public int add(int num1, int num2) {
        System.out.println("add方法的參數是["+num1+","+num2+"]");
        int result = num1+num2;
        System.out.println("add方法的結果是"+result);
        return result;
    }

    public int sub(int num1, int num2) {
        System.out.println("sub方法的參數是["+num1+","+num2+"]");
        int result = num1-num2;
        System.out.println("sub方法的結果是"+result);
        return result;
    }

    public int mul(int num1, int num2) {
        System.out.println("mul方法的參數是["+num1+","+num2+"]");
        int result = num1*num2;
        System.out.println("mul方法的結果是"+result);
        return result;
    }

    public int div(int num1, int num2) {
        System.out.println("div方法的參數是["+num1+","+num2+"]");
        int result = num1/num2;
        System.out.println("div方法的結果是"+result);
        return result;
    }
}

上述代碼中,日誌信息和業務邏輯的耦合性很高,不利於系統的維護,使用 AOP 可以進行優化,如何來實現 AOP?使用動態代理的方式來實現。

給業務代碼找一個代理,打印日誌信息的工作交個代理來做,這樣的話業務代碼就只需要關注自身的業務即可。

package com.southwind.utils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class MyInvocationHandler implements InvocationHandler {
    //接收委託對象
    private Object object = null;

    //返回代理對象
    public Object bind(Object object){
        this.object = object;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+"方法的參數是:"+ Arrays.toString(args));
        Object result = method.invoke(this.object,args);
        System.out.println(method.getName()+"的結果是"+result);
        return result;
    }
}

以上是通過動態代理實現 AOP 的過程,比較複雜,不好理解,Spring 框架對 AOP 進行了封裝,使用 Spring 框架可以用面向對象的思想來實現 AOP。

Spring 框架中不需要創建 InvocationHandler,只需要創建一個切面對象,將所有的非業務代碼在切面對象中完成即可,Spring 框架底層會自動根據切面類以及目標類生成一個代理對象。

LoggerAspect

package com.southwind.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Component
public class LoggerAspect {
	// .* 切割所有方法  (..)和所有參數
    @Before(value = "execution(public int com.southwind.utils.impl.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        //獲取方法名
        String name = joinPoint.getSignature().getName();
        //獲取參數
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println(name+"方法的參數是:"+ args);
    }

    @After(value = "execution(public int com.southwind.utils.impl.CalImpl.*(..))")
    public void after(JoinPoint joinPoint){
        //獲取方法名
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法執行完畢");
    }

    @AfterReturning(value = "execution(public int com.southwind.utils.impl.CalImpl.*(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        //獲取方法名
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法的結果是"+result);
    }

    @AfterThrowing(value = "execution(public int com.southwind.utils.impl.CalImpl.*(..))",throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint,Exception exception){
        //獲取方法名
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法拋出異常:"+exception);
    }

}

LoggerAspect 類定義處添加的兩個註解:

  • @Aspect:表示該類是切面類。
  • @Component:將該類的對象注入到 IoC 容器。

具體方法處添加的註解:

@Before:表示方法執行的具體位置和時機。

CalImpl 也需要添加 @Component,交給 IoC 容器來管理。

package com.southwind.utils.impl;

import com.southwind.utils.Cal;
import org.springframework.stereotype.Component;

@Component
public class CalImpl implements Cal {
    public int add(int num1, int num2) {
        int result = num1+num2;
        return result;
    }

    public int sub(int num1, int num2) {
        int result = num1-num2;
        return result;
    }

    public int mul(int num1, int num2) {
        int result = num1*num2;
        return result;
    }

    public int div(int num1, int num2) {
        int result = num1/num2;
        return result;
    }
}

spring.xml 中配置 AOP。

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

    <!-- 自動掃描 -->
    <context:component-scan base-package="com.southwind"></context:component-scan>

    <!-- 是Aspect註解生效,爲目標類自動生成代理對象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

context:component-scancom.southwind 包中的所有類進行掃描,如果該類同時添加了 @Component,則將該類掃描到 IoC 容器中,即 IoC 管理它的對象。

aop:aspectj-autoproxy 讓 Spring 框架結合切面類和目標類自動生成動態代理對象。

  • 切面:橫切關注點被模塊化的抽象對象。
  • 通知:切面對象完成的工作。
  • 目標:被通知的對象,即被橫切的對象。
  • 代理:切面、通知、目標混合之後的對象。
  • 連接點:通知要插入業務代碼的具體位置。
  • 切點:AOP 通過切點定位到連接點。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章