Spring学习笔记

第一章 引言

1.EJB存在的问题(Enterprise Java Bean)

  • 运行环境苛刻,造价高,扩展性,定制型差

    tomcat核心东西,servlet引擎

    weblogic、websphere ——applicationServer 收费且不开源

  • 代码移植性差

    总结:EJB重量级框架

2.什么是Spring

Spring是一个轻量级的JavaEE解决方案,聚合众多优秀的设计模式

轻量级:
  • 对于运行环境没有额外要求
  • 可以让选择开源(tomcat)的或者收费(weblogic)的环境,直接运行在服务器上
  • 代码移植性高(不需要实现额外接口)

javaEE的解决方案

概念:

Spring是一个javaEE开源的轻量级框架,可以解决企业开发中的难题,让编码更加简单,核心组件IOC容器和AOC面向切面编程。

  • IOC控制反转:把整个对象的创建过程,统一交给SpringIOC容器处理,底层使用反射+工厂模式实现。
  • AOP面向切面编程:对我们的功能(方法)前实现增强,减少代码的冗余,事务,权限管理,底层是基于动态代理模式实现的。
整合设计模式:
  • 工厂
  • 代理
  • 模板
  • 策略

3.设计模式

广义概念:

面向对象设计中,解决特定问题的经典代码

狭义概念:

GOF 4人帮定义的23设计模式:工厂、设配器、装饰器、门面、代理、模板等等的

4.工厂设计模式

概念:通过工厂类创建对象

传统方式创建对象:User user=new User();

好处:解耦合(实现类硬编码在程序中就会存在耦合),耦合较高的话不利于代码维护

对象的创建方式:
  1. 直接调用构造方法创建对象

    UserService userService =new UserServiceImpl();

  2. 通过反射的形式创建对象,解耦合(反射+定义配置文件)

    • 获取Class类对象:Classs c1= Class.forName(“com.yxnu.UserServiceImpl”);
    • 创建对象:UserService userservice=(UserService ) c1.newInstance();
    • 但是反射创建时候,”com.yxnu.UserServiceImpl”还是存在耦合,那我们通过properties配置文件解决。
    • 解决userDao耦合方式和一样,工厂模式创建(反射+定义配置文件)
Properties配置文件:
  • Properties 集合 存储Propertie文件的内容
  • 特殊的Map key和value都必须是String类型的,key=String value=String
  • Properties [userService=com.yxnu.UserServiceImpl]
  • 读取值:Properties.getProperty(“userService”);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
假设创建好了ClassName.properties
userService=com.yxnu.UserServiceImpl


private static Properties pro=new Properties();
//因为io流读取耗费系统资源,避免重复性打开io,最好是程序启动的时候一次性读取内容,所以用static块
static{
//第一步 获得IO流
InputStream input= 类名.class.getResourceAsStream("/ClassName.properties");
//第二步 文件内容封装到Properties集合中,key=userService value=com.yxnu.UserServiceImpl
pro.load(input);
input.close();
}

Classs c1= Class.forName(pro.getProperty("userService"));

方法:

  1. 一个方法申明(五部分):修饰符 返回值类型 函数名 参数列表 可选项
1
public static void test(String str) throw Exception{}

2.方法实现

set注入
  1. 提供set和get方法

    真正用到的是set方法,即使没有创建变量,只要有set方法spring容器依然可以创建对象

  2. spring配置文件

    • bean标签内采用property标签进行赋值,以及简化配置和p命名空间简化配置
构造注入
  1. ==提供有参构造方法==

  2. spring的配置文件

    • bean标签内采用constructor-arg标签进行赋值,constructor-arg标签个数和顺序和构造方法参数一一对应
构造方法重载
  1. 参数个数不同

    控制constructor-arg标签的数量控制

  2. 参数个数相同

    通过<constructor-arg type="">来进行控制

注入的总结

未来的实战中,引用set注入还是构造注入?

答:set注入更多

  1. 构造注入麻烦(重载)
  2. spring框架底层大量应用了set注入

第七章 反转控制和依赖注入

1.反转(转移)控制(IOC Inverse Of Control)

控制:对于成员变量赋值的控制权

==反转控制:==把对于成员变量的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成。

  • 好处:解耦合

底层实现:工厂设计模式

2.依赖注入(Dependency Injection DI)

注入:通过Spring的工厂以及配置文件,为对象(bean,组件)的成员变量赋值

依赖注入:当一个类需要另外一个类时,就意味着依赖,一旦出现依赖,就可以把另外一个类作为本类的成员变量,最终通过spring配置文件进行注入(赋值),好处:解耦合

1
2
3
4
5
6
7
UserService类依赖UserDao,所以
public class UserService
{
private UserDao userDao;
set和get方法
}
spring的配置文件进行赋值,注入

第八章 Spring工厂创建复杂对象

1.什么是复杂对象

简单对象:指的是可以直接通过new 构造方法 创建对象

复杂对象:指的是不能直接通过new 构造方法 创建对象

1
比如JDBC的Connection对象

2.Spring工厂创建复杂对象的三种方式

  1. FactoryBean接口

    实现FactoryBean接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class MyFactoryBean implements FactoryBean{
    public Object getObject(){
    //用于书写创建复杂对象的代码,并把复杂对象作为方法的返回值返回
    }

    public Class getObjectType(){
    //返回所创建对象的Class对象
    }

    public boolean isSingleton(){
    //需要创建一次
    return true
    //每一次调用 都需要创建一个新的复杂对象
    return false
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package org.example.FactoryBean;

    import org.springframework.beans.factory.FactoryBean;

    import java.sql.Connection;
    import java.sql.DriverManager;

    public class ConnectionFactoryBean implements FactoryBean<Connection> {
    //用于书写创建复杂对象的代码
    @Override
    public Connection getObject() throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql:///wxf", "root", "123456");
    return conn;
    }
    //获得所创建的复杂对象的Class
    @Override
    public Class<?> getObjectType() {
    return Connection.class;
    }
    //true表示只创建一次,false表示每次都创建
    @Override
    public boolean isSingleton() {
    return false;
    }
    }
    Spring配置文件的配置
    1
    2
    如果class中指定的类是FactoryBean接口的实现类,那么通过id获取的是这个类所创建的复杂对象,Connection对象
    <bean id="conn" class="com." />
    细节
    • 如果就想获得FactoryBean类型的对象,ctx.getBean("&conn");获得的就是ConnectionFactoryBean对象

    • mysql高版本在连接创建时,需要制定SSL证书,时区设置

      1
      url="jdbc:mysql://localhost:3306/test?useSSL=false&?serverTimezone=UTC"
    • isSingleton方法

      • 返回true只会创建一个复杂对象
      • 返回false每一次都会创建新的对象
      • 所以根据这个对象的特点来决定是true还是false,Connection为false,因为如果两个人共用一个连接对象,期中一个事务完成提交,但是另外一个并没有完成?所以是每次创建新的对象
    • 依赖注入的体会:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      把ConnectionFactoryBean中依赖的4个字符串信息,进行配置文件的注入,解耦合

      1.
      private String DriverClassName ;
      private String url;
      private String username;
      private String password;
      生成set和get方法,用于set注入

      2.
      <bean id="conn" class="org.example.FactoryBean.ConnectionFactoryBean">
      <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql:///wxf?serverTimezone=UTC"/>
      <property name="username" value="root"/>
      <property name="password" value="123456"/>
      </bean>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      package org.example.FactoryBean;

      import org.springframework.beans.factory.FactoryBean;

      import java.sql.Connection;
      import java.sql.DriverManager;

      public class ConnectionFactoryBean implements FactoryBean<Connection> {
      private String DriverClassName ;
      private String url;
      private String username;
      private String password;

      public String getDriverClassName() {
      return DriverClassName;
      }

      public void setDriverClassName(String driverClassName) {
      DriverClassName = driverClassName;
      }

      public String getUrl() {
      return url;
      }

      public void setUrl(String url) {
      this.url = url;
      }

      public String getUsername() {
      return username;
      }

      public void setUsername(String username) {
      this.username = username;
      }

      public String getPassword() {
      return password;
      }

      public void setPassword(String password) {
      this.password = password;
      }

      //用于书写创建复杂对象的代码
      @Override
      public Connection getObject() throws Exception {
      Class.forName(DriverClassName);
      Connection conn = DriverManager.getConnection(url,username,password);
      return conn;
      }
      //获得所创建的复杂对象的Class
      @Override
      public Class<?> getObjectType() {
      return Connection.class;
      }
      //true表示只创建一次,false表示每次都创建
      @Override
      public boolean isSingleton() {
      return false;
      }
      }
    FactoryBean的实现原理(简易版)

    接口回调

    1. 为什么Spring规定FactoryBean接口来实现,并且getObject()?
    2. ctx.getBean(“conn”)获得的是复杂对象,而不是获得ConnectionFactoryBean对象ctx.getBean(“&conn”)?
    Spring内部运行流程
    1
    Connection conn = (Connection) ctx.getBean("conn");
    1. 通过conn读取配置文件bean标签相关信息,conn获得ConnectionFactoryBean类的对象,进而通过instanceof判断是否是FactoryBean接口的实现类

      1
      <bean id="conn" class="org.example.FactoryBean.ConnectionFactoryBean">
    2. Spring按照规定找到你写的复杂对象创建,调用getObject,获得Connection对象

      1
      2
      3
      4
      5
      6
      7
      //用于书写创建复杂对象的代码
      @Override
      public Connection getObject() throws Exception {
      Class.forName(DriverClassName);
      Connection conn = DriverManager.getConnection(url,username,password);
      return conn;
      }
    3. 返回Connection对象

      1
      <bean id="conn" class="org.example.FactoryBean.ConnectionFactoryBean">
    FactoryBean总结

    Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,在Spring整合其他框架时,大量应用FactoryBean

  2. 实例工厂

    好处:
    • 避免Spring框架的侵入
    • 整合遗留系统(公司遗留的系统)
    开发步骤:
    • 公司已经有遗留的系统,ConnectionFactory

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class ConnectionFactory {
      public Connection getConnection(){
      Connection conn=null;
      try {
      Class.forName("com.mysql.cj.jdbc.Driver");
      conn= DriverManager.getConnection("jdbc:mysql:///wxf?serverTimezone=UTC","root","123456");
      } catch (ClassNotFoundException | SQLException e) {
      e.printStackTrace();
      }
      return conn;
      }
      }
    • 配置文件

      1
      2
      <bean id="connFactory" class="org.example.FactoryBean.ConnectionFactory"/>
      <bean id="conn1" factory-bean="connFactory" factory-method="getConnection"/>
  3. 静态工厂

    开发步骤
    • ```java
      /**
      • 直接StaticConnectionFactory.getConnection();
      • /
        public class StaticConnectionFactory {
        public static Connection getConnection(){
           Connection conn=null;
           try {
               Class.forName("com.mysql.cj.jdbc.Driver");
               conn= DriverManager.getConnection("jdbc:mysql:///wxf?serverTimezone=UTC","root","123456");
           } catch (ClassNotFoundException | SQLException e) {
               e.printStackTrace();
           }
           return conn;
        
        }
        }
        1
        2
        3
        4
        5

        - 配置文件

        ```xml
        <bean id="conn2" class="org.example.FactoryBean.StaticConnectionFactory" factory-method="getConnection"/>

3.Spring工厂创建对象的总结

简单对象:
1
<bean id="" class=""/>
复杂对象:
1
2
3
1.FactoryBean
2.实例工厂
3.静态工厂

两种对象的创建方式注入都可以采用上面提到set注入和构造方法注入

第九章 控制Spring工厂创建对象的次数

1.如何控制简单对象的创建次数

1
2
3
Spring默认是singleton,singleton只会创建一次,prototype每一次都会创建新的对象
<bean id="account" scope="singleton" class="org.example.scope.Account"/>
<bean id="account" scope="prototype" class="org.example.scope.Account"/>

2.如何控制复杂对象的创建次数

1
2
3
4
5
6
7
FactoryBean{
isSingleton(){
return true; 只会创建一次
return false; 每一次都会创建
}
}
如果没有isSingleton方法,还是通过scope属性进行对象创建次数的控制

3.为什么要控制对象的控制次数

好处:节省不必要的内存浪费

什么样的对象只创建一次?

  • SqlSessionFactory
  • DAO
  • 无状态的Service

什么样的对象每一次都要创建?

  • Connection,因为要控制事务不可能大家共用
  • SqlSession Session
  • Controler

如果能被共用,线程是安全的,创建一次即可,如果不能被共用或者线程是不安全的,则需要每次都创建

第十章 对象的生命周期

1.什么是对象的生命周期

指的是一个对象创建、存活、消亡的一个完整过程

2.为什么要学习对象的生命周期

由Spring负责对象的创建、存活、销毁,了解生命周期,有利于使用好Spring为什么创建对象

3.生命周期的三个阶段

  • 创建阶段

    spring工厂如何创建对象

    1
    scope="singleton", spring工厂创建的同时,完成对象的创建

    注意:如果想设置scope=”singleton”这种情况下,也需要在获取对象的同时,创建对象,设置lazy-init=”true”

    1
    2
    3
    scope="prototype",spring工厂会在获取对象的同时,创建对象
    什么叫获取对象?
    ctx.getBean("user");
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void test1(){
    //1.获得Spring工厂,对应scope="singleton",这个时候对象已经创建完成了,如果想获取的同时创建对象,加上lazy-init="true"
    //对于scope="prototype",在获取对象的时候,getBean()时才创建对象
    ApplicationContext ctx=new ClassPathXmlApplicationContext("/applicationContext.xml");
    //2.通过工厂类来获取(拿到)对象
    Person person= (Person) ctx.getBean("person");
    System.out.println(person);
    }

    代码演示

    1
    2
    3
    4
    5
    public class Product {
    public Product() {
    System.out.println("创建对象的时候回调用默认的无参构造");
    }
    }
    1
    2
    <!--对象的生命周期-->
    <bean id="product" scope="singleton" class="org.example.life.Product" lazy-init="true"/>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 测试对象的生命周期
    */
    @Test
    public void test13(){
    //Spring工厂在创建的时候,就创建了对象
    ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
    //Spring工厂会在获取对象的同时,创建对象
    ctx.getBean("product");
    }
    }
  • 初始化阶段

    Spring工厂在创建完对象后,调用对象的初始化方法,完成对应的初始化操作

    1.初始化方法提供:程序员根据需求,提供初始化方法,最终完成初始化操作

    2.初始化方法调用:Spring工厂进行调用

    • initialzingBean接口(好处就是接口是Spring定义的,不需要额外配置)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //程序员根据需求,实现的方法,完成初始化操作
    afterPropertiesSet()

    public class Product implements InitializingBean {
    /**
    * 这个就是初始化方法:做一些初始化操作
    * Spring会进行调用
    */
    @Override
    public void afterPropertiesSet() throws Exception {
    System.out.println("Spring会进行初始化的调用");
    }
    }
    • 对象中提供一个普通的方法(好处:不需要耦合Spring框架,不需要实现Spring规定的接口,但是就是需要额外配置文件)

      1
      2
      3
      4
      5
      public void myInit(){
      System.out.println("对象中提供初始化的调用");
      }

      <bean id="product" class="" init-method="myInit"/>
    • 细节分析

      1. 如果一个对象既实现initialzingBean接口,同时又提供了普通的初始化方法?

        答:都会执行,顺序是先执行initalzingBean接口的初始化方法,再执行普通的初始化方法

      2. ==注入发生在初始化之前==

      3. 什么叫做初始化操作?

        答:资源的初始化:数据库 IO 网络

  • 销毁阶段

    Spring在销毁对象前,会调用对象的销毁方法,完成销毁操作

    1.Spring什么时候销毁所创建的对象?

    答:ctx.close()

    2.销毁方法:程序员根据自己需求,定义销毁方法,完成销毁操作,Spring工厂会完成调用

    • DisposableBean

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class Product implements InitializingBean, DisposableBean {
      @Override
      public void afterPropertiesSet() throws Exception {
      System.out.println("Spring会进行初始化的调用");
      }

      @Override
      public void destroy() throws Exception {
      System.out.println("实现DisposableBean接口的销毁方法");
      }

      public void myClose(){
      System.out.println("自定义的销毁方法");
      }
      }
      1
      2
      3
      4
      5
      ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
      //因为ctx.close()是子类的方法,父类没办法调用,所以改成父类ClassPathXmlApplicationContext
      ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
      ctx.getBean("product");
      ctx.close();
    • 定义一个普通的销毁方法

      1
      2
      3
      4
      5
      public void myClose(){
      System.out.println("自定义的销毁方法");
      }

      <bean id="product" scope="singleton" class="" init-method="myInit" destroy-method="myClose"/>
    • 细节分析:

      1. 销毁方法的操作只适用于 scope="singleton"

      2. 什么叫做销毁操作

        答:主要值得是,资源的释放操作,io.close(),connection.close()

    生命周期的总结:

    先创建对象,然后注入(赋值),再初始化(初始化如果使用的是spring提供的方法,会先调用spring的初始化方法再调用自定义的初始化方法),最后销毁(销毁和初始化同样的,如果使用的spring的销毁方法,先调用spring销毁方法再调用自定义的)

    image-20210906151340707

第十一章 配置文件参数化

把Spring配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中

1.spring的配置文件中存在需要经常修改的字符串吗?

答:存在的,比如数据库连接相关的参数,(通常在spring配置文件中是字符串,而且需要经常修改

2.经常变化的字符串,在spring配置文件中,直接修改的话

答:不利于项目维护(修改)

3.转移到一个小的配置文件,propertises,利于后序修改

4.好处:利于spring配置文件的维护(修改)

1.配置文件参数化的开发步骤

  • 提供一个小的配置文件(.properties),名字和位置随便

    1
    2
    3
    4
    jdbc.driverClassName=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql:///wxf?serverTimezone=UTC
    jdbc.username=root
    jdbc.password=123456
  • 把小配置文件和spring配置文件进行整合

    1
    2
    <!--spring配置文件和小配置文件进行整合-->
    <context:property-placeholder location="classpath:db.properties"/>
  • 在spring配置文件中通过**${key}**获取小配置文件中对应的值

    1
    2
    3
    4
    5
    6
    <bean id="conn" class="org.example.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>

​ 后续需要改这块的信息,只需要修改db.properties文件的内容即可

第十二章 自定义类型转化器

1.类型转换器(convert接口)

为什么是接口?

答:因为接口可以多实现,可能你需要多种类型转换。

作用:spring通过类型转化器把配置文件中字符串类型的数据转换成了对象中成员变量所对应类型的数据,进而完成注入

2.自定义类型转换器

原因:

当spring内部没有提供特定类型转换器时,而程序员在应用的过程中需要使用,那么就需要程序员自定义类型转换器。

比如在set注入时, 实体类中private Date birthday,此时就会发现spring自带的类型转换器不能转换成,因为每个国家的日期格式都不一样,spring不好做出判断满足需要,所以就需要程序员自定义类型转换器。

步骤:

  1. 实现convert接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class MyDateConvert implements Converter<String, Date> {
    /**
    * convert方法作用:String类型转换为Date类型<String,Date>
    * SimpleDateFormat sdf=new SimpleDateFormat();
    * sdf.praset(String);
    * param:source 代表的是配置文件中日期字符串,<value>2021-9-6<value/>
    * return:当把转换好的Date作为convert方法的返回值,spring自动为birthday属性进行注入
    */
    @Override
    public Date convert(String s) {
    Date parse = null;
    try {
    SimpleDateFormat sdf=new SimpleDateFormat("YYYY-MM-DD");
    parse = sdf.parse(s);
    } catch (ParseException e) {
    e.printStackTrace();
    }
    return parse;
    }
    }
  2. 在spring配置文件中进行注册

  • 创建MyDateConvert对象创建出来

    1
    <bean id="myDateConvert" class="org.example.convert.MyDateConvert"/>
  • 类型转换器的注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    目的:告诉spring,我们所创建的MyDateConvert是一个类型转化器
    <!--自定义类型转换器-->
    <bean id="birthday" class="org.example.convert.Person">
    <property name="birthday" value="2021-9-6"/>
    </bean>


    <!--spring创建MyDateConvert类型对象-->
    <bean id="myDateConvert" class="org.example.convert.MyDateConvert"/>
    <!--spring提供了注册对象的类ConversionServiceFactoryBean-->
    <!--用于注册类型转换器-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
    <!--因为ConversionServiceFactoryBean中的变量converters是set集合-->
    <set>
    <ref bean="myDateConvert"/>
    </set>
    </property>
    </bean>

细节

  • 把MyDateConvert中的日期格式,通过set注入,解耦合。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private String pattern;

    public void setPattern(String pattern) {
    this.pattern = pattern;
    }

    public String getPattern() {
    return pattern;
    }
    1
    2
    3
    <bean id="myDateConvert" class="org.example.convert.MyDateConvert">
    <property name="pattern" value="YYYY-MM-DD"/>
    </bean>
  • ConversionServiceFactoryBean定义时,id必须是conversionService,大写都不能变,一定得是这个

  • spring框架内置日期类型的转换器

​ 日期格式:2021/9/6 ,不支持2021-9-6这种格式

第十三章 后置处理Bean

总结:先构造,然后注入,如果实现InitializingBean调用afterPropertiesSet方法,最后调用配置指定的init-method方法,单例,destory方法,多实例,垃圾回收

BeanPostProcessor作用:对spring工厂所创建的对象,进行再加工

1
2
3
4
注意:BeanPostProcessor接口
xxxx(){
用户自己定义的加工方法
}

BeanPostProcessor运行原理和流程

image-20210906223028699

程序员实现BeanPostProcessor接口中规定的方法

1
2
3
4
5
6
Object postProcessorBeforeInitiallization(Object bean,String beanName)
作用:spring创建完成对象,并进行注入后,可以运行Before方法进行加工
通过方法的参数Object bean,String beanName获得spring创建好的对象,然后进行加工,最终通过返回值返回对象给spring完成后续操作

Object postProcessorAfterInitiallization(Object bean,String beanName)
和Before方法一样,只是After方法是在对象的初始化操作完成后,才可以运行的

实战中,很少做初始化操作,那么这个时候就没有必要区分Before还是After,只需要取其中一个方法即可(After)

BeanPostProcessor开发步骤

1.类实现BeanPostProcessor接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Catrgory catrgory= (Catrgory) bean;
catrgory.setId(777777);
return catrgory;
}
}

2.spring配置文件中进行配置

1
2
3
4
5
6
<bean id="category" class="org.example.beanpost.Catrgory">
<property name="id" value="100"/>
<property name="name" value="IP手机"/>
</bean>

<bean id="myBeanPostProcessor" class="org.example.beanpost.MyBeanPostProcessor"/>

3.BeanPostProcessor细节

BeanPostProcessor会对spring工厂中所有创建的对象进行加工

1
2
3
4
5
6
7
8
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Catrgory){
Catrgory catrgory= (Catrgory) bean;
catrgory.setId(777777);
}

return bean;
}

AOP编程

第一章 静态代理设计模式

1.为什么需要代理设计模式?

1.1 问题
  • 在javaEE分层开发中,哪个层次对于我们来讲最重要

    答:Service层最为重要

  • Service层包含了哪些代码?

    答:Service层=核心功能(几十行 上百行代码)+ 额外功能

    ​ 核心功能:业务运算,Dao调用

    ​ 额外功能:不属于业务,可有可无,代码量小,比如事务,日志,性能等等的

  • 额外功能书写在Service层好不好?

    答:Service层的调用者角度(controler):比如事务功能就希望额外增加

    ​ (软件设计者):不需要额外功能,以便修改出错

  • 现实生活中解决方式:

    image-20210907154319879

2.代理设计模式

概念:

通过代理类,为原始类(目标)增加额外的功能

好处:利于原始类(目标)类的维护

名词解释:

原始类(目标类):指的是业务类,只负责核心功能

目标方法(原始方法):指的就是原始类中的方法

额外功能(附加功能):日志、事务、性能等等的

代理开发的核心要素:

代理类=原始类(目标类)+ 额外功能 + 和原始类实现相同的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface UserService{
m1
m2
}

public UserServiceImpl implements UserService{
m1————业务运算,Dao调用
m2
}

public UserServiceProxy implements UserService{
m1
m2
}
编码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserServiceProxy implements UserService{
//还需要原始类
private UserServiceImpl userService= new UserServiceImpl();
@Override
public void reg(User user) {
System.out.println("增加日志功能");
//还需要调用原始类的功能
userService.reg(user);
}
@Override
public boolean login(String name, String password) {
System.out.println("增加日志功能");
return userService.login(name,password);
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 测试静态代理模式
*/
@Test
public void test1() {
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceProxy userServiceProxy = (UserServiceProxy) ctx.getBean("userServiceProxy");
userServiceProxy.login("张三", "123456");
userServiceProxy.reg((User) ctx.getBean("user"));
}
静态代理存在的问题
  1. 静态类文件数量过多,不利于项目管理,

    1
    2
    UserServiceImpl UserServiceProxy
    OrderServiceImpl OrderServiceProxy
  2. 额外功能维护性差

    如果需要修改代理类中的额外功能,那么每一个代理类都需要修改

第二章 Spring的动态代理开发

1.Spring动态代理的概念

概念:通过代理类为原始类增加额外功能,从概念上和静态代理一样

好处:利于原始类的维护

2.搭建开发环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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.8</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>

3.Spring动态代理的开发步骤

  1. 创建原始对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class UserServiceImpl implements UserService{
    @Override
    public void reg(User user) {
    System.out.println("注册:业务运算+Dao");
    }

    @Override
    public boolean login(String name, String password) {
    System.out.println("登录");
    return true;
    }
    }
    1
    <bean id="userServiceImpl" class="yxnu.edu.proxy.UserServiceImpl"/>
  2. 提供额外功能

    MethodBeforeAdivice接口

    额外的功能书写在接口的实现中,运行在原始类方法运行之前

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Before implements MethodBeforeAdvice {
    /**
    * 作用:需要把运行在原始方法执行之前的额外功能,书写在before中
    */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
    System.out.println("---method before advice log---");
    }
    }
    1
    <bean id="before" class="yxnu.edu.dynamic.Before"/>
  3. 定义切入点(配置文件中完成)

    切入点:额外功能加入的位置,根据需要比如加入在register,login,这就叫切入点

    1
    2
    3
    4
    <aop:config>
    <!--所有的方法,都作为切入点,加入额外功能-->
    <aop:pointcut id="pc" expression="execution(* *(..))"/>
    </aop:config>
  4. 组装(2 3整合)

    把切入点和额外功能进行整合

    1
    2
    3
    <!--组装:目的是把切入点和额外功能进行整合-->
    <!--所有的方法,login,register都加入before这个额外功能-->
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
  5. 调用

    目的:获得spring工厂创建的动态代理对象,并进行调用

    注意:

    1. ==spring工厂通过原始对象id值获得的是代理对象==
    2. 获得代理对象后,可以通过声明接口类型,并进行对象的存储
    1
    2
    3
    4
    ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService=(UserService)ctx,getBean("userServiceImpl");
    userService.reg();
    userService.login();

4.spring动态代理的细节

  1. sprin创建的动态代理类在哪里?

    spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。

    什么叫做动态字节码技术:通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束时,动态字节码跟着消失。

    结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理的类文件数量过多,影响项目管理的问题。

  2. 动态代理编程简化代理的开发

    在额外功能不改变的情况下,创建其他原始类的代理对象时,只需要指定原始类对象即可。

  3. 动态代理额外功能维护性增强

    打开扩展,关闭修改

    如果之前的额外功能方法不满足需求了,需要新的额外功能,这个时候只需要增加新的额外功能方法,然后修改配置文件即可,尽量开闭原则

    1
    <bean id="before" class="yxnu.edu.dynamic.NewBefore"/>

第三章 spring动态代理详解

1.额外功能详解

MethodBeforeAdvice分析

1.MethodBeforeAdvice接口作用:额外需要运行在原始方法之前,进行额外功能操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Before implements MethodBeforeAdvice {
/**
* 作用:需要把运行在原始方法执行之前的额外功能,书写在before中
* Method:额外功能所增加给的那个原始方法
* login方法、reg方法
* Object[]:额外功能所增加给的那个原始方法的参数,String name,String password
* 因为是数组,所以可以接收多个对象,并且是Object类型的,可以是任何类型的
* Object:原始对象,UserServiceImpl OrderServiceImpl
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("---method before advice log---");
}
}

2.before方法的3个参数在实战中如何使用

答:根据需要进行使用,有可能用到有可能用不到

MethodInterceptor(方法拦截器)

可以在原始方法前、后、前后、抛异常时使用,甚至可以改变原始方法返回值运行结果

MethodBeforeAdvice——只能在原始方法执行之前,而MethodInterceptor可以在原始方法执行前或者后或者前后都可以使用

代码演示:
  • 额外功能运行在原始方法之前:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Arround implements MethodInterceptor {
/**
* invoke方法的作用:额外功能书写在原始方法之前、后
* 确定:原始方法怎么运行
* 参数:MethodInvocation,额外功能所增加给的那个原始方法
* methodInvocation.proceed(),原始方法的运行
* 返回值:Object,原始方法的返回值,因为Object可以装一切类,所以用Object
*/

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("在原始方法之前的额外功能,日志");
Object rem = methodInvocation.proceed();
return rem;
}
}
  • 额外功能运行在原始方法之后:
1
2
3
4
5
6
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object rem = methodInvocation.proceed();
System.out.println("在原始方法之后的额外功能,日志");
return rem;
}
  • 额外功能运行在原始方法之前之后:
1
2
3
4
5
6
7
8
9
10
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("在原始方法之前的额外功能,日志");
Object rem = methodInvocation.proceed();
System.out.println("在原始方法之后的额外功能,日志");
return rem;
}

什么样的方法需要在原始方法运行之前之后都添加呢?
答:事务
  • 额外功能原始方法抛出异常的时候
1
2
3
4
5
6
7
8
9
10
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object rem = null;
try {
rem = methodInvocation.proceed();
} catch (Exception e) {
System.out.println("在原始方法抛出异常时,执行额外功能");
}
return rem;
}
MethodInterceptor影响原始方法的返回值

原始方法的返回值,直接作为invoke方法的返回值,是不会影响原始方法的返回值

但是,MethodInterceptor也是可以影响原始方法的返回值的,invoke方法的返回值不要返回原始方法的返回运行结果即可。

2.切入点详解

切入点决定额外功能加入位置(主要指在哪些方法上加切入点)

1
2
<!--execution(* *(..))匹配了所有方法-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
1
2
execution()  切入点函数
* *(..) 切入点表达式

切入点表达式

1.方法切入点表达式
1
2
3
定义一个方法:
public void test(int i,int j)
* * (..)
1
2
3
4
第一个*代表修饰符和返回值
第二个*代表方法名
()代表参数列表
..代表对于参数没有要求(参数有无都行,参数有几个都行,参数什么类型都行)
定义login方法作为切入点
1
* login(..)
定义login方法,且有两个字符串类型的参数作为切入点
1
2
3
4
5
6
7
* login(String,String)

非java.lang包中的类型,必须要写全限定名
* login(yxnu.edu.entity.User)

只对第一个参数类型有限定,其余参数有几个,是什么类型没有限制
* login(String,..)
上面所讲解的方法切入点不精准

image-20210908010052679

精准的方法切入点限定
1
2
修饰符返回值   包.类.方法(参数)
* yxnu.edu.proxy.UserServiceImpl.reg(..)
2.类切入点

指定特定的类作为切入点,自然这个类中的所有方法,都会加上对应的额外功能

1
<aop:pointcut id="1" expression="execution(* yxnu.edu.proxy.UserServiceImpl.*(..))">

这个类中的所有方法都作为切入点,加入额外功能

假设a、b两个包都有一个同样名称的类,这样的话就需要把包名写成*,忽略包

1
2
3
4
//这是一级包的写法
<aop:pointcut id="1" expression="execution(* *.UserServiceImpl.*(..))">
//两个点,这是二级包乃至多级包的写法
<aop:pointcut id="1" expression="execution(* *..UserServiceImpl.*(..))">
3.包切入点 ——更具实战性

指定包作为额外功能加入的位置,包中所有的类和方法都会加入额外功能

1
2
3
4
//必须确保所有切入点所在的类在proxy包中,不能是proxy子包
<aop:pointcut id="1" expression="execution(* yxnu.edu.proxy.*.*(..))">
//两个点,当前包乃至当前包子包
<aop:pointcut id="1" expression="execution(* yxnu.edu.proxy..*.*(..))">

切入点函数

执行切入点表达式

1.execution

  • 最为重要的切入点函数,功能最全
  • 可以执行方法切入点表达式、类切入点、包切入点
  • 弊端:书写麻烦
  • 注意:其他切入点函数,是简化execution书写复杂度的,功能上是一样的

2.args

  • 作用:主要用于函数(方法)参数的匹配
  • 切入点:方法参数必须得是2个字符串类型的参数
  • execution(* *(String,String)) args(String,String)

3.within

  • 作用:主要用于进行类、包切入点表达式的匹配
  • 切入点:UserServiceImpl这个类
  • 类切入点:execution(* *..UserServiceImpl.*(..)) within(*..UserServiceImpl)
  • 包切入点:within(yxnu.edu.proxy..*)

4.@annotation

  • 作用:具有特殊注解的方法加入额外功能

  • ```xml
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Log {
    }

    @Log
    @Override
    public void reg(User user) {
        System.out.println("注册:业务运算+Dao");
    

    // throw new RuntimeException(“测试异常”);

    }
    

    <aop:pointcut id=”pc” expression=”@annotation(yxnu.edu.proxy.Log)”/>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13



    #### 切入点函数的逻辑运算

    指的是 **整合多个切入点函数一起配合**工作,进而完成更为复杂的需求

    - and操作

    ```xml
    案列:方法login 参数为2个字符串
    execution(* login(String,String))
    execution(* login(..)) and args(String,String)
    1
    2
    <!--切入函数的逻辑运算-->
    <aop:pointcut id="pc" expression="execution(* login(..)) and args(String,String)"/>

    注意:and操作不能用于两边都是同种类型的切入函数

  • or操作

    1
    2
    案例:register方法和login方法作为切入点
    executin(* login(..)) or execution(* register(..))

总结:

为什么需要动态代理类?

  • 通过代理类为原始类(目标类)增加额外功能,

  • 好处:利于原始类的维护

  • 开发步骤:

    • 目标对象(原始对象)

    • 额外功能

      MethodInterceptor接口中的invoke方法

    • 切入点

      切入点表达式

      切入点函数

    • 组装

      将切入点和额外功能整合在一起

第四章 Aop编程

1.Aop概念

AOP(Aspect Oriented Programing) 面向切面编程

以切面为基本单位的程序开发,。。。

切面=切入点+额外功能

00P 面向对象编程

以对象为基本单位的程序开发,通过对象间的彼此协调,相互协调,完成程序的构建

POP 面向过程编程

以过程(函数、方法)为基本单位的程序开发,通过过程间的彼此协调,相互协调,完成程序的构建

本质就是Spring的动态代理开发,通过动态代理类为原始类增加额外功能,好处:有利于原始类的维护

注意:Aop编程不可能取代OOP编程,它是OOP的补充

2.Aop编程的开发步骤

1.原始对象

2.额外功能

3.切入点

4.组装切面

3.切面的名词解释

切面=切入点+额外功能

image-20211003155446618

第五章 AOP的底层实现原理

1.核心问题

  1. AOP如何创建多态代理类

  2. Spring的工厂是如何加工创建代理对象

    通过原始对象的id值获得的是代理对象

2.动态代理类的创建

2.1 JDK的动态代理

  • Proxy.newProxyInstance方法参数详解

image-20211004111610129

image-20211004110759739

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class JdkProxy {
public static void main(String[] args) {
/**
* 1.借用类加载器 可以随便借
* 2.JDK 8.0 以前如果使用的是外部类要加上fina修饰符
* fina UserServiceImpl userService = new UserServiceImpl();
*/
//1.创建原始对象
UserServiceImpl 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("user1", "123456");
userServiceProxy.reg(new User());
}

}

2.2 CGlib的动态代理

  • CGlib创建动态代理类的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时还可以在代理类中增加新的实现。

image-20211004145402195

  • 编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class CGlib {
public static void main(String[] args) {
//1.创建原始对象
UserService userService = new UserService();
//2.通过CGlib创建动态代理对象
/**
* Proxy.newProxyInstance(classLoder,interface,invocationhandler)
*
* Enhancer.setClassLoder()
* Enhancer.setSuperClass()
* Enhancer.setCallback()————实现MethodInterceptor接口,cglib的
* Enhancer.create()————创建代理类
*/
Enhancer enhancer=new Enhancer();
enhancer.setClassLoader(CGlib.class.getClassLoader());
enhancer.setSuperclass(userService.getClass());
MethodInterceptor methodInterceptor = new MethodInterceptor() {
//等同于InvocationHandler中的invoke方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("————cglib log before————");
Object ret = method.invoke(userService, args);
return ret;
}
};
enhancer.setCallback(methodInterceptor);
UserService CglibProxy = (UserService) enhancer.create();
CglibProxy.login("user1","123456");
CglibProxy.reg(new User());

}

}

2.3 总结

  1. JDK创建动态代理 ——Proxy.newProxyInstance() 通过接口创建代理的实现类
  2. CGlib创建动态代理——Enhancer 通过继承父类创建的代理类

3.Spring工厂如何加工原始对象

  • 思路分析

    image-20211004153024877

第六章 基于注解的AOP编程

1.基于注解的AOP编程开发步骤

  1. 原始对象

  2. 额外功能

  3. 切入点

  4. 组装切面

    通过切面类定义了 额外功能、切入点 @Around("execution(* login(..))")

    ​ 切面类 @Aspect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 1.额外功能
* public class MyArround implements MethodInterceptor{
* public Object invoke(MethodInvocation invocation){
* Object ret= invocation.proceed();
* return ret;
* }
* }
* 2.切入点
* <aop:config
* <aop:pointcut id="" exepression=""
*/

@Aspect
public class MyAspect {
@Around("execution(* login(String,String))")
public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("aspect log");
Object ret = joinPoint.proceed();
return ret;
}
}
1
2
3
<bean id="aspect" class="yxnu.edu.aspect.MyAspect"/>
<!--告诉Spring基于注解进行Aop编程-->
<aop:aspectj-autoproxy/>
1
2
3
4
5
6
7
8
9
10
/**
* 测试@Aspect注解
*/
@Test
public void test4(){
ApplicationContext ctx=new ClassPathXmlApplicationContext("/ApplicationContext1.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login("admin", "123");
userService.reg(new User());
}

2.细节

1.切入点复用

在切面类中定义一个函数,加上@Pointcut注解,通过这种方式定义切入点表达式,后续更加有利于切入点复用、项目的维护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Aspect
public class MyAspect {
@Pointcut("execution(* login(..))")
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 transaction");
Object ret = joinPoint.proceed();
return ret;
}

}

2.动态代理的创建方式

AOP底层实现 两种代理创建方式

1
2
1.JDK 通过实现接口创建代理对象
2.CGlib 通过继承父类创建代理对象

默认情况下,spring使用的是jdk动态代理创建对象

如果要切换成CGlib

  • 基于注解AOP开发

    1
    <aop:aspectj-autoproxy proxy-target-class="true"/>
  • 传统的AOP开发

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

第七章 AOP开发中的一个坑

坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法加入了额外功能,而内部的方法,通过普通方式调用,是原始类对象调用的方法,无法加入额外功能,如果想让内层的方法也调用代理对象的方法,就需要获得工厂,进而获得代理对象。

ApplicationContextAware接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx=applicationContext;
}
/**
* this.reg()调用的是本类(原始类)对象的reg()方法
* 需要通过工厂创建代理对象来调用reg()方法
* 因为spring工厂是一个重量级资源,在一个应用中,应该只创建一个工厂
* 所以需要把创建好的工厂传过来
* 实现ApplicationContextAware接口把创建好的工厂传过来
*
*/
@Override
public void login(String name, String pwd) {
System.out.println("aspect login");
UserService userService = (UserService) ctx.getBean("userService");
userService.reg(new User());
// this.reg(new User());
}

@Override
public void reg(User user) {
System.out.println("aspect reg");
}

}

第八章 AOP阶段知识总结

image-20211004202843179