Spring5筆記

視頻

概述

Tomcat Javaweb服務器 幫助我們去集中去處理請求和響應 包括完成Servlet以及相關代碼的運行和解析

而其中Servlet引擎 最核心的功能

Controller 控制器
Service 業務處理
DAO 數據庫的訪問和操作
DB 數據庫


設計模式

  1. ⼴義概念

    ⾯向對象設計中,解決特定問題的經典代碼

  2. 狹義概念

    GOF4⼈幫定義的23種設計模式:⼯⼚、適配器、裝飾器、⻔⾯、代理、模板… GOF4 (面向對象領域的4個大師)

工廠模式

好處:解耦合

對象的創建方式:

1.直接調用構造方法創建對象 UserService userService=new UserServiceImpl(); 把接口的實現類硬編碼在程序中

這行代碼有強關聯關係,如果想改變UserServiceImpl這行代碼,那麼整個類都要改變

//UserServiceImpl userService = new UserServiceImpl();

//這裏只需要更改BeanFactory就可以更改UserServiceImpl
UserService userService = BeanFactory.getUserservice(); // return new UserServiceImpl();

public class BeanFactory {
    public static UserService getUserservice(){
        return new UserServiceImpl();
    }
}

2.通過反射的形式創建對象解耦合

public class BeanFactory {
    public static UserService getUserservice() throws Exception {
            Class<?> clazz = Class.forName("com.zhang.basic.UserServiceImpl");
            return (UserService) clazz.newInstance();
    }
}


//配置文件的方式
private static final Properties env = new Properties() ;
static {
    InputStream resourceAsStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
    try {
        env.load(resourceAsStream);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static UserService getUserservice() throws Exception {
    Class<?> clazz = Class.forName(env.getProperty("UserService"));
    return (UserService) clazz.newInstance();
}
#只需要更改配置文件的值就可以更改對應的UserServiceImpl
UserService = com.zhang.basic.UserServiceImpl		

方法聲明5個要素 修飾符、返回值類型、方法名、參數表、異常(可選)

通用工廠文件

public class BeanFactory {
    private static Properties env = new Properties();
	//IO 系統級資源 儘量避免重複打開IO 而且最好是在程序啓動的時候一次性讀取想要的內容  用靜態代碼塊的方式來完成
    static{
        try {
            //爲避免耦合 類名最好寫在配置文件中
            //第一步 獲得IO輸入流
            InputStream inputStream=BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            //第二步 文件內容到封裝 Properties集合中 key = userService value = com.baizhixx.UserServiceImpl
            env.load(inputStream);       
            inputStream.close();   //關閉IO輸入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
/* 這裏結構所使用的代碼基本一致 只有小配置文件的key是有所區別的
 * 所以爲每一個對象提供一個獨立的工廠是沒有價值和意義的
 */
     public static Object getBean(String key){
         Object ret = null;
         try {
             Class clazz = Class.forName(env.getProperty(key));
             ret = clazz.newInstance();
         } catch (Exception e) {
            e.printStackTrace();
         }
         return ret;
     }
    
/*  test文件中  */   
@Test
public void test1() {
    UserService userService = (UserService)BeanFactory.getBean("userService");
	//返回的是Object但實際需要的是UserService  所以需要強制類型轉換
 
    userService.login("name", "suns");	//具體user	Service對象的使用
    User user = new User("suns", "123456");
    userService.register(user);
}

將文件內容封裝到集合中

Java Properties類:用於讀取java的配置文件

https://www.cnblogs.com/bakari/p/3562244.html

load ( InputStream inStream),從輸入流中讀取屬性列表(鍵和元素對)。通過對指定的文件(比如說xxx.properties 文件)進行裝載來獲取該文件中的所有鍵值對。以供 getProperty ( String key) 來搜索。

第一個Spring程序

環境配置

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

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.14.RELEASE</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>
<?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="person" class="com.zhang.basic.Person"/>
</beans>
@org.junit.Test
    public void test3(){
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Person person = (Person) ctx.getBean("person");
}

Spring⼯⼚的相關的⽅法

//第一種方式
Person person = (Person) ctx.getBean("person");
//第二種方式		通過這種⽅式獲得對象,就不需要強制類型轉換
Person person =ctx.getBean("person",Person.class);
//第三種方式
//當前Spring的配置⽂件中 只能有⼀個bean class是Person類型
Person person =ctx.getBean(Person.class);
//獲取的是 Spring工廠配置文件中所有bean標籤的id值  person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println("beanDefinitionName = " + beanDefinitionName);
}

//根據類型獲得Spring配置文件中對應的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
    System.out.println("id = " + id);
}

//用於判斷是否存在指定id值得bean,不能判斷name值
if (ctx.containsBeanDefinition("person")) {
    System.out.println("true = " + true);
}else{
    System.out.println("false = " + false);
}


//用於判斷是否存在指定id值得bean,也可以判斷name值
if (ctx.containsBean("p")) {
    System.out.println("true = " + true);
}else{
    System.out.println("false = " + false);
}

注入

通過Spring⼯⼚及配置⽂件,爲所創建對象的成員變量賦值

爲什麼需要注⼊?

通過編碼的⽅式,爲成員變量進⾏賦值,存在耦合

好處:解耦合

如何進⾏注⼊?

  • 類的成員變量提供set get⽅法
  • 配置spring的配置⽂件

String+8種基本類型

private Integer id;
private String name;
//Getter and Setter
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
<bean id="person" name="p" class="com.baizhiedu.basic.Person">
	<property name="id">
		<value>20</value>
	</property>
	<property name="name">
		<value>zy</value>
	</property>
</bean>

基於屬性簡化

<!--	value 屬性只能簡化8種基本類型 + String 注入標籤-->

<bean id="person" name="p" class="com.baizhiedu.basic.Person">
    <property name="id" value="20"/>
    <property name="name" value="zy"/>
</bean>

基於p命名空間的簡化

P:中間按alt+enter 選擇Create namespace declaration

<bean id="person" name="p" class="com.baizhiedu.basic.Person" p:id="20" p:name="zy"/>

數組

<property name="addresses">
  <list>
      <value>zpark</value>
      <value>shangdi</value>
      <value>xierq</value>
      <value>xierq</value>
      <value>xierq</value>
  </list>
</property>

Set集合

<property name="tels">
    <set>
        <value>138111111</value>
        <value>139111111</value>
        <value>166111111</value>
        <value>166111111</value>
        <value>166111111</value>
    </set>
</property>

List集合

<property name="addresses">
  <list>
      <value>zpark</value>
      <value>shangdi</value>
      <value>xierq</value>
      <value>xierq</value>
      <value>xierq</value>
  </list>
</property>

Map集合

<!--注意: map -- entry -- key有特定的   標籤 <key></key>
 						  值根據對應類型選擇對應類型的標籤-->
<property name="qqs">
  <map>
     <entry>
         <key><value>suns</value></key>
         <value>3434334343</value>
     </entry>
     <entry>
         <key><value>chenyn</value></key>
         <value>73737373</value>
     </entry>
  </map>
</property>
Map<String, String> qqs = person.getQqs();
Set<String> keys = qqs.keySet();    //通過keySet獲得對象裏的key    然後通過對象.get(key)獲得value
for (String key : keys) {
    System.out.println(key + " value is " + qqs.get(key));
}

Properites

<!--Properties類型 特殊的Map key=String
						   value=String-->
<props>
     <prop key="key1">
         value1
    </prop>
    
     <prop key="key2">
         value2
    </prop>
</props>

爲用戶自定義類型賦值

public class UserServiceImpl implements UserService {
//    private UserDAO userDAO = new UserDAOImpl();
//    private UserDAO userDAO = (UserDAO)BeanFactory.getBean("userDAO");
    private UserDAO userDAO;		//具體的賦值使用配置文件來完成->解耦合
    
    public UserDAO getUserDAO() {
        return userDAO;
    }
    public void setUserDAO(UserDAO userDAO) {  //通過set方法爲UserDAO對象賦值
        this.userDAO = userDAO;
    }
    
    @Override
    public void register(User user) {
        userDAO.save(user);
    }
    @Override
    public void login(String name, String password) {
        userDAO.queryUserByNameAndPassword(name, password);
    }
}

userService接口裏的UserServiceImpl想要使用UserDAO接口裏的UserDAOImpl類的方法

 <bean id="userService" class="com.baizhiedu.basic.UserServiceImpl">
       <property name="userDAO">
           <bean class="com.baizhiedu.basic.UserDAOImpl"/>
       </property>
 </bean>
@Test
public void test10() {
   ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
   UserService userService = (UserService) ctx.getBean("userService");

   userService.register(new User("suns", "123456"));   //UserDAO裏面的方法可以使用
   userService.login("xiaohei", "999999");
}

自定義類型賦值第二種方式

第⼀種賦值⽅式存在的問題:

  1. 配置⽂件代碼冗餘;
  2. 被注入的對象 (UserDAO)多次創建,浪費(JVM)內存資源。
<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
    <!--這裏的name是Setter裏的形參  public void setUserDAO(UserDAO userDAO) {this.userDAO = userDAO; }-->
    <!--														   ↑						       -->
    <property name="userDAO">		 
        <ref bean="userDAO"/>
    </property>
</bean>

基於屬性簡化

<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO"/>
</bean>

基於p命名空間的簡化

<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.baizhiedu.service.UserServiceImpl" p:userDAO-ref="userDAO"/>

構造注入

public class Customer {
    private String name;
    private int age;
    
    public Customer(String name) {this.name = name;}
    
    public Customer(int age) {this.age = age;}

    public Customer(String name, int age) {  	//被重載的方法必須改變參數列表(參數個數或類型或順序不一樣);
        this.name = name;
        this.age = age;
    }
}
<bean id="customer" class="com.baizhiedu.basic.constructer.Customer">
    <constructor-arg type="int">
        <value>20</value>
    </constructor-arg>
</bean>

反轉控制

反轉控制(IOC Inverse of Control)

控制:對於成員變量賦值的控制權;

反轉控制:把對於成員變量賦值的控制權,從代碼中轉移(反轉)到 Spring ⼯⼚和配置⽂件中完成。

好處:解耦合;

底層實現:工廠設計模式;

依賴注入

依賴注入 (Dependency Injection - DI)

注⼊:通過 Spring 的⼯⼚及配置⽂件,爲對象(bean,組件)的成員變量賦值;
依賴注⼊:當⼀個類需要另⼀個類時,就意味着依賴,⼀旦出現依賴,就可以把另⼀個類作爲本類的成員變量,最終通過 Spring 配置⽂件進⾏注⼊(賦值)
好處:解耦合;
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-w6khT5az-1592187674958)(photo/image-20200611221048923.png)]

FactoryBean 接口

public class ConnectionFactoryBean implements FactoryBean<Connection> {
    // 用於書寫創建複雜對象時的代碼
    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring", "root", "1234");
        return conn;
    }

    // 返回創建的複雜對象的類型
    @Override
    public Class<Connection> getObjectType() {
        return Connection.class;
    }

    // 是否單例
    @Override
    public boolean isSingleton() {
        return false; // 每一次都創建新的複雜對象
        // return true; // 只創建一次這種類型的複雜對象
    }
}

<!--class 指定了 ConnectionFactoryBean, 獲得的是該類創建的複雜對象 Connection -->
<bean id="conn" class="com.baizhiedu.factorybean.ConnectionFactoryBean"/>

如果就想獲得 FactoryBean 類型的對象,加個 &ctx.getBean("&conn")

ConnectionFactoryBean cfb = (ConnectionFactoryBean) ctx.getBean("&conn");

Spring 內部運行流程:

  1. 配置文件中通過 id conn 獲得 ConnectionFactoryBean 類的對象 ,進而通過 instanceof 判斷出是 FactoryBean 接⼝的實現類;
  2. Spring 按照規定 getObject() —> Connection;
  3. 返回 Connection;

實例工廠

  1. 避免 Spring 框架的侵⼊;
  2. 整合遺留系統;
public class ConnectionFactory {
    public Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useSSL=false", "root", "1234");
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}
<!--實例工廠-->
<!-- 先創建出工廠實例 		實例方法必須先有對象  再有對應的方法調用	-->
<bean id="connFactory" class="com.baizhiedu.factorybean.ConnectionFactory"/>
 <!-- 通過工廠實例裏的方法創建複雜對象 -->
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>

靜態工廠

<!--靜態工廠-->
<bean id="conn" class="com.baizhiedu.factorybean.StaticFactoryBean" factory-method="getConnection"/>

控制 Spring 工廠創建對象的次數

控制簡單對象的創建次數

配置文件中進行配置:
sigleton:只會創建⼀次簡單對象,默認值;
prototype:每⼀次都會創建新的對象;

<!--控制簡單對象創建次數-->
<bean id="scope" scope="singleton" class="com.baizhiedu.scope.Scope"/>

控制複雜對象的創建次數

如果是 FactoryBean 方式創建的複雜對象:

public class xxxFactoryBean implements FactoryBean {
	public boolean isSingleton() {
		return true; // 只會創建⼀次
		// return false; // 每⼀次都會創建新的
	}
}

對象的生命週期

創建階段

Spring 工廠何時創建對象?

  1. scope="prototype":Spring 工廠在獲取對象 ctx.getBean("xxx") 的同時,創建對象。
  2. scope="singleton":Spring 工廠創建的同時,創建對象。
    通過配置 <bean lazy-init="true"/> 也可以實現工廠獲取對象的同時,創建對象。

初始化階段

什麼時候?Spring 工廠在創建完對象後,調用對象的初始化方法,完成對應的初始化操作。
初始化方法提供:程序員根據需求,提供初始化方法,最終完成初始化操作。
初始化方法調用:Spring 工廠進行調用。

提供初始化方法的兩種方式:

InitializingBean 接口:

public cass Product implements InitializingBean {
	//程序員根據需求實現的方法, 完成初始化操作
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Product.afterPropertiesSet");
    }
}

對象中提供一個普通的初始化方法,配置文件種配置 init-method

public class Product {
    public void myInit() {
        System.out.println("Product.myInit");
    }
}
<bean id="product" class="com.baizhiedu.life.Product" init-method="myInit"/>
  1. 如果⼀個對象既實現 InitializingBean 同時⼜提供的 普通的初始化方法,執行順序?
    先執行 InitializingBean,再執行 普通初始化方法。
  2. 注入⼀定發⽣在初始化操作的前面。

銷燬階段

Spring 銷燬對象前,會調用對象的銷燬方法,完成銷燬操作。
Spring 什麼時候銷燬所創建的對象?ctx.close();
銷燬方法提供:程序員根據業務需求,定義銷燬方法,完成銷燬操作
銷燬方法調用:Spring 工廠進行調用。

開發流程與初始化操作一樣,提供銷燬方法的兩種方式:

DisposableBean 接口:

public class Product implements DisposableBean {
    // 程序員根據⾃⼰的需求, 定義銷燬方法, 完成銷燬操作
    @Override
    public void destroy() throws Exception {
        System.out.println("Product.destroy");
    }
}

對象中提供一個普通的銷燬方法,配置文件種配置 destroy-method

public class Product {
	// 程序員根據⾃⼰的需求, 定義銷燬方法, 完成銷燬操作
    public void myDestory() {
        System.out.println("Product.myDestory");
    }
}
<bean id="product" class="com.baizhiedu.life.Product" destroy-method="myDestory"/>
  1. 銷燬方法的操作只適用於 scope="singleton",初始化操作都適用。
  2. 銷燬操作到底是什麼?
    資源的釋放:io.close()connection.close()、…

配置文件參數化

${key}

jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring?useSSL=false
jdbc.username = root
jdbc.password = 1234
<!--Spring的配置文件與⼩配置文件進行整合	classpath代表類路徑:class文件夾這個路徑-->
<!--resources 下的文件在整個程序編譯完後會被放到 classpath 目錄下,src.main.java中的文件也是-->
<context:property-placeholder location="classpath:/db.properties"/>

<!--	在 Spring 配置文件中通過 ${key} 獲取小配置文件中對應的值	-->
<bean id="conn" class="com.baizhiedu.factorybean.ConnectionFactoryBean">
	<property name="driverClassName" value="${jdbc.driverClassName}"/>
	<property name="url" value="${jdbc.url}"/>
	<property name="username" value="${jdbc.username}"/>
	<property name="password" value="${jdbc.password}"/>
</bean>

自定義類型轉換器

public class MyDateConverter implements Converter<String, Date> {   //<原始類型,轉換好的類型>
    /*
     convert方法作用: String ---> Date
     SimpleDateFormat sdf = new SimpleDateFormat();
     sdf.parset(String) ---> Date

     參數:
     source : 代表的是配置文件中, 日期字符串 <value>2020-10-11</value>
     return : 當把轉換好的 Date 作爲 convert 方法的返回值後,
             Spring ⾃動的爲birthday屬性進行注入(賦值)
    */
    @Override
    public Date convert(String source) {     //聲明的是Data類型的轉換器
        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            date = sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}
<!--創建 MyDateConverter 對象-->
<bean id="myDateConverter" class="com.baizhiedu.converter.MyDateConverter"/>
<!--用於註冊類型轉換器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <ref bean="myDateConverter"/>
        </set>
    </property>
</bean>

<bean id="good" class="com.baizhiedu.converter.Good">
    <property name="name" value="zy"/>
    <property name="birthday" value="2020-6-12"/>
</bean>

細節處理

MyDateConverter 中的日期的格式,通過 依賴注入 的方式,由配置文件完成賦值。

public class MyDateConverter implements Converter<String, Date> {
    private String pattern;

    @Override
    public Date convert(String source) {
        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(pattern);
            date = sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
}
<!-- 配置文件完成對日期格式的賦值 -->
<bean id="myDateConverter" class="com.baizhiedu.converter.MyDateConverter">
    <property name="pattern" value="yyyy-MM-dd"/>
</bean>

ConversionSeviceFactoryBean 定義 id屬性,值必須是 conversionService

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <ref bean="myDateConverter"/>
        </set>
    </property>
</bean>

Spring 框架其實內置了日期類型的轉換器:日期格式必須是 2020/06/12

<bean id="good" class="com.baizhiedu.converter.Good">
	<property name="name" value="zhenyu"/>
	<property name="birthday" value="2012/12/12"/>
</bean>

後置處理 Bean

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return null;			//Object bean, String beanName  類名  對象名
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof  Category) {    //BeanPostProcessor 會對 Spring 工廠創建的所有對象進行加工  
            Category category = (Category) bean;
            category.setName("baizhiedu");
            return category;
        }
        return bean;
    }
}
<bean id="myBeanPostProcessor" class="com.baizhiedu.beanpost.MyBeanPostProcessor"/>

靜態代理

靜態代理:爲每⼀個原始類,手工編寫⼀個代理類(.java .class)

接口

public interface UserService {
	void register(User user);
	boolean login(String name, String password);
}

原始類

public class UserServiceImpl implements UserService {
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 業務運算 + DAO");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 業務運算 + DAO");
        return true;
    }
}

代理類

代理類是爲原始類添加額外功能的

/**
 * 靜態代理類編碼實現
 */
public class UserServiceProxy implements UserService { // 實現原始類相同的接口
    private UserService userService = new UserServiceImpl(); // 代理類中必須有原始類
    @Override
    public void register(User user) {
        System.out.println("---log---"); // 額外功能
        userService.register(user);
    }
    @Override
    public boolean login(String name, String password) {
        System.out.println("---log---"); // 額外功能
        return userService.login(name, password);
    }
}

動態代理開發

搭建開發環境

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.1.14.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.8.9</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>

創建原始對象(目標對象)

public interface UserService {
    void register(User user);
    boolean login(String name, String password);
}
public class UserServiceImpl implements UserService {
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 業務運算 + DAO");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 業務運算 + DAO");
        return true;
    }
}

額外功能

MethodBeforeAdvice 接口

public class Before implements MethodBeforeAdvice {
    /**
     * 作用: 把需要運行在原始方法執行之前運行的額外功能, 書寫在 before 方法中
     */
    @Override
    public void before(Method method, Object[] objects, Object target) throws Throwable {
        System.out.println("---method before advice log---");
    }
}
<!-- 額外功能 -->
<bean id="before" class="com.baizhiedu.aop.Before"/>

定義 切入點:額外功能的加入

⽬的: 由程序員根據⾃⼰的需要,決定額外功能加入給哪個原始方法(register、login)

<!--所有方法都做爲切入點,加入額外的功能(register、login)-->
<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
</aop:config>

組裝

<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
    <!--組裝:目的把切入點與額外功能進行整合-->
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>

調用

//Spring 的工廠通過原始對象的 id 值獲得的是代理對象
//獲得代理對象後,可以通過聲明接口類型,進行對象的存儲
@Test
public void test() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    UserService userService = (UserService) ctx.getBean("userService");
    userService.login("admin", "1234");
    userService.register(new User());
}

動態字節碼技術:通過第三方動態字節碼框架,在 JVM 中創建對應類的字節碼,進而創建對象,當虛擬機結束,動態字節碼跟着消失。

結論:動態代理不需要定義類文件,都是 JVM 運行過程中動態創建的,所以不會造成類⽂件數量過多,影響項目管理的問題。
動態代理使得 額外功能的維護性大大增強

動態代理開發詳解

MethodBeforeAdvice

作用:需要把運行在原始方法執行之前運行的額外功能,書寫在before方法中

Method: 額外功能所增加給的那個原始方法
login方法

register方法

showOrder方法

Object[]: 額外功能所增加給的那個原始方法的參數。String name,String password
User

Object: 額外功能所增加給的那個原始對象 UserServiceImpl
OrderServiceImpl

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZgcXD4kA-1592187674960)(photo/image-20200612191140807.png)]

@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
	System.out.println("-----new method before advice log------");
}

MethodInterceptor(方法攔截器)

public class Around implements MethodInterceptor {    //比如事務、拋出異常
    @Override
    public Object invoke(MethodInvocation Invocation) throws Throwable {
    	System.out.println("---額外功能運行在原始方法執行之前---");
        Object ret = Invocation.proceed(); // 原始方法運行, 獲取原始方法的返回值
        System.out.println("---額外功能運行在原始方法執行之後---");
        return ret;
    }
}
<!-- 額外功能 -->
<bean id="around" class="com.baizhiedu.dynamic.Around"/>

<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
    <!--組裝:目的把切入點與額外功能進行整合-->
    <aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>

切入點表達式

方法切入點

定義一個方法
public void add(int i, int j)
   *               *      (..)
* * (..)    --> 所有方法

*  --->  修飾符 返回值
*  --->  方法名
() --->  參數表
.. --->  對於參數沒有要求 (參數有沒有,參數有⼏個都行,參數是什麼類型的都行)
<!-- 定義login作爲切入點 -->
<aop:pointcut id="pc" expression="execution(* login (..))"/>

<!-- 定義register作爲切入點 -->
<aop:pointcut id="pc" expression="execution(* register (..))"/>

精準方法切入點限定

<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.login(..))"/>

<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.login(String, String))"/>

類切入點

# 類中所有的方法加入了額外功能
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.*(..))"/>
# 忽略包
1. 類只存在一級包
<aop:pointcut id="pc" expression="execution(* *.UserServiceImpl.*(..))"/>
2. 類存在多級包
<aop:pointcut id="pc" expression="execution(* *..UserServiceImpl.*(..))"/>

包切入點(實戰中用的多)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zrWzk3qF-1592187674962)(photo/image-20200613094556878.png)]

# 切入點包中的所有類,必須在proxy中,不能在proxy包的⼦包中
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.*.*(..))"/>
# 切入點當前包及其⼦包都生效
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy..*.*(..))"/>

切入點函數(execution、args、within)

args

args 作用:主要用於 函數(方法) 參數的匹配

切入點:方法參數必須得是 2 個字符串類型的參數

# 使用 execution
<aop:pointcut id="pc" expression="execution(* *(String, String))"/>

# 使用 args
<aop:pointcut id="pc" expression="args(String, String)"/>

within

within 作用:主要用於進行 類、包切入點表達式 的匹配。

切入點: UserServiceImpl 這個類

# 使用 execution
<aop:pointcut id="pc" expression="expression(* *..UserServiceImpl.*(..))"/>

# 使用 within
<aop:pointcut id="pc" expression="within(*..UserServiceImpl)"/>
切入點: com.baizhiedu.proxy 這個包

# 使用 execution
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy..*.*(..)"/>

# 使用 within
<aop:pointcut id="pc" expression="within(com.baizhiedu.proxy..*)"/>

@annotation

作用:爲具有特殊註解的 方法 加入額外功能。

@Target(ElementType.METHOD)     //註解需要用在方法上面
@Retention(RetentionPolicy.RUNTIME)   //註解什麼時候起作用
public @interface Log {
}
<aop:pointcut id="pc" expression="@annotation(com.baizhiedu.Log)"/>

切入點函數的邏輯運算(and、or)

and 與操作
案例: 方法名叫 login 同時 參數是 2個字符串
# execution
<aop:pointcut id="pc" expression="execution(* login(String, String))"/>
# execution and args
<aop:pointcut id="pc" expression="execution(* login(..)) and args(String, String))"/>
注意:與操作不能⽤於同種類型的切⼊點函數
以下這個是錯誤的, 因爲不存在同時叫 login 和 register 的方法
<aop:pointcut id="pc" expression="execution(* login(..)) and execution(* register(..))"/>

or 或操作:
案例: 方法名叫 register 或 login 的⽅法作爲切⼊點
<aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>

AOP

AOP (Aspect Oriented Programing)
⾯向切⾯編程 = Spring動態代理開發
以切⾯爲基本單位的程序開發,通過切⾯間的彼此協同,相互調⽤,完成程序的構建
切⾯ = 切⼊點 + 額外功能
OOP (Object Oritened Programing)
⾯向對象編程 Java
以對象爲基本單位的程序開發,通過對象間的彼此協同,相互調⽤,完成程序的構建
POP (Producer Oriented Programing)
⾯向過程(⽅法、函數)編程 C
以過程爲基本單位的程序開發,通過過程間的彼此協同,相互調⽤,完成程序的構建
AOP的概念:
本質就是Spring得動態代理開發,通過代理類爲原始類增加額外功能。
好處:利於原始類的維護
注意:AOP編程不可能取代OOP,OOP編程有意補充。

JDK 的動態代理

在這裏插入圖片描述
在這裏插入圖片描述

public class TestJDKProxy {
    /*
        1. 借用類加載器  TestJDKProxy
                       UserServiceImpl
        2. JDK8.x前
            final UserService userService = new UserServiceImpl();
     */
    public static void main(String[] args) {
        //1 創建原始對象
        UserService userService = new UserServiceImpl();
        //2 JDK創建動態代理
        InvocationHandler handler = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("------proxy  log --------");       
                Object ret = method.invoke(userService, args);	 //原始方法運行
                return ret;
            }
        };
        UserService userServiceProxy = (UserService)Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
        userServiceProxy.login("suns", "123456");
        userServiceProxy.register(new User());
    }
}

CGlib

CGlib創建動態代理的原理:⽗⼦繼承關係創建代理對象,原始類作爲⽗類,代理類作爲⼦類,這樣既可以保證2者⽅法⼀致,同時在代理類中提供新的實現(額外功能+原始⽅法)

public class UserService {
    public void login(String name, String password) {
        System.out.println("UserService.login");
    }

    public void register(User user) {
        System.out.println("UserService.register");
    }
}
package com.baizhiedu.cglib;

import com.baizhiedu.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TestCglib {
    public static void main(String[] args) {
        //1 創建原始對象
        UserService userService = new UserService();

        /*
          2 通過cglib方式創建動態代理對象
            Proxy.newProxyInstance(classloader,interface,invocationhandler)

            Enhancer.setClassLoader()
            Enhancer.setSuperClass()
            Enhancer.setCallback();  ---> MethodInterceptor(cglib)
            Enhancer.create() ---> 代理
         */

        Enhancer enhancer = new Enhancer();

        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());

        MethodInterceptor interceptor = new MethodInterceptor() {
            //等同於 InvocationHandler --- invoke
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("---cglib log----");
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };

        enhancer.setCallback(interceptor);
        UserService userServiceProxy = (UserService) enhancer.create();
        userServiceProxy.login("suns", "123345");
        userServiceProxy.register(new User());
    }
}

Spring 工廠如何加工原始對象

思路分析:主要通過 BeanPostProcessor 將原始對象加工爲代理對象

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LOFIyzv6-1592187674965)(photo/image-20200613151751854.png)]

public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    /*
         Proxy.newProxyInstance();
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("----- new Log-----");
                Object ret = method.invoke(bean, args);
                return ret;
            }
        };
      return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
    }
}

基於註解的 AOP 編程的開發

# 通過切⾯類 定義了 額外功能 @Around

​ 定義了 切⼊點 @Around(“execution(* login(…))”)

​ @Aspect 切⾯類

/*
       1. 額外功能
                 public class MyArround implements MethodInterceptor{
                      public Object invoke(MethodInvocation invocation){
                              Object ret = invocation.proceed();
                              return ret;
                      }
                 }
       2. 切入點
             <aop:config
                 <aop:pointcut id=""  expression="execution(* login(..))"/>
 */

@Aspect
public class MyAspect {
    @Around("execution(* login(..))")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----aspect log ------");
        Object ret =joinPoint.proceed();
        return ret;
    }
}
<bean id="userService" class="com.baizhiedu.aspect.UserServiceImpl"/>
<!--
   切面
	 1. 額外功能
	 2. 切入點
	 3. 組裝切面
-->
<bean id="arround" class="com.baizhiedu.aspect.MyAspect"/>
<!--告知Spring基於註解進行AOP編程-->
<aop:aspectj-autoproxy proxy-target-class="true" />

切入點複用

切入點複用:在切面類中定義⼀個函數,上面用 @Pointcut 註解。
通過這種方式定義切入點表達式,後續更加有利於切入點複用。

@Aspect
public class MyAspect {
    @Pointcut("execution(* *..UserServiceImpl.*(..))")
    public void myPointcut(){}

    @Around(value="myPointcut()")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----aspect log ------");
        Object ret = joinPoint.proceed();
        return ret;
    }

    @Around(value="myPointcut()")
    public Object arround1(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----aspect tx ------");
        Object ret = joinPoint.proceed();
        return ret;
    }
}

默認情況 AOP 編程 底層應用 JDK動態代理創建方式

基於註解的 AOP 開發 中切換爲 Cglib:

<aop:aspectj-autoproxy proxy-target-class="true"/>

傳統的 AOP 開發 中切換爲 Cglib:

<aop:config proxy-target-class="true">
	...
</aop:config>

AOP 開發中的一個坑

public class UserServiceImpl implements UserService, ApplicationContextAware {
    private ApplicationContext ctx;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              this.ctx = applicationContext;
    }
    @Log
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 業務運算 + DAO ");
        //throw new RuntimeException("測試異常");
        //調用的是原始對象的login方法 ---> 核心功能
        /*
            設計目的:代理對象的login方法 --->  額外功能+核心功能
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext2.xml");
            UserService userService = (UserService) ctx.getBean("userService");
            userService.login();

            Spring工廠重量級資源 一個應用中 應該只創建一個工廠
         */
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("suns", "123456");
    }
    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}
 Object ret = joinPoint.proceed();
    return ret;
}

}


**默認情況 AOP 編程 底層應用 JDK動態代理創建方式**。

基於註解的 AOP 開發 中切換爲 Cglib:

```xml
<aop:aspectj-autoproxy proxy-target-class="true"/>

傳統的 AOP 開發 中切換爲 Cglib:

<aop:config proxy-target-class="true">
	...
</aop:config>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章