背景

  • 传统javaweb开发的困惑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //用户账户信息修改业务方法
    public void updateUserInfo(User user){
    try{
    //开启事务
    DaoUtils.openTransaction();
    //获得UserDao执行插入User数据到数据库操作
    UserDao userDao = new UserDaoImpl();
    userDao.updateUserInfo(user);
    //修改成功后,向用户行为日志表中插入一条数据,内容:修改时间等信息
    userLog.recodeUserUpdate(user);
    //提交事务
    DaoUtils.commit();
    }catch (Exception e){
    //回滚事务
    DaoUtils.rollback();
    //向异常日志表中插入数据
    ExceptionLog exceptionLog = new ExceptionLogImpl();
    exceptionLog.recodeException(this, e);
    }
    }
    • 问题一:层与层之间紧密耦合在了一起,接口与具体实现紧密耦合在了一起。

    • 解决思路:程序代码中不要手动new对象,而是找第三方根据要求为程序提供需要的Bean对象。
      image-20250217144758060

    • 问题二:通用的事务功能(日志)耦合在业务代码中。

    • 解决思路:程序代码中不要手动new对象,而是找第三方根据要求为程序提供需要的Bean对象的代理对象。
      image-20250217145338971

  • IoC, DI和AOP思想提出

    • 背景image-20250217145625713
    • IoC (inversion of Control), 控制反转,强调的是原来在程序中创建Bean的权利反转给第三方。
    • DI (Dependency Injection), 依赖注入,强调的是Bean之间关系,这种关系第三方负责去设置。
    • AOP (Aspect Oriented Programming), 面向切面编程,功能的横向抽取,主要的实现方式就是Proxy。
    • 框架
      • 框架framework是基于基础技术之上,从众多业务中抽取出的通用解决方案。
      • 框架是一个半成品,使用框架规定的语法开发可以提高开发效率,可以用简单的代码就能完成复杂的基础业务。
      • 框架内部使用大量的设计模式、算法、底层代码操作技术,如反射、内省、xml解析、注解解析等。
      • 框架一般都具备扩展性
      • 有了框架,我们就可以将精力尽可能的投入在纯业务开发上而不用去费心技术实现以及一些辅助业务。
    • Java框架
      • 基础框架:完成基本业务操作的框架,如MyBatis, Spring, SpringMVC, Struts2, Hibernate等。
      • 服务框架:特定领域的框架,一般还可以对外提供服务框架,如MQ, ES, Nacos等。
  • Spring框架的诞生

    • Spring框架概述
      开源轻量级java开发应用框架,生态完善不管哪个领域都有解决方案,官网

    • Spring框架历史

      • jsp开发,MVC+三层架构,开发成本高。
      • EJB重量级框架。
      • Spring出现+SSH->Spring不可取代+SSM
      • Spring生态,拿来主义
      • SpringBoot出现
      • SpringCloud出现
    • Spring框架技术栈
      image-20250218112229298

    • BeanFactory快速入门
      BeanFactory开发步骤
      image-20250218112547774
      第三方代表的就是BeanFactory,beans.xml代表配置清单

      1. 导入Spring的jar包或Maven坐标。
        1
        2
        3
        4
        5
        6
        7
        8
        <!-- pom.xml -->
        <dependencies>
        <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.1.14</version>
        </dependency>
        </dependencies>
      2. 定义UserService接口及其UserServiceImpl实现类。
        目录结构
        image-20250218173139221

        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
        //UserService.java
        package com.itheima.service;

        public interface UserService {
        }

        //UserServiceImpl.java
        package com.itheima.service.impl;

        import com.itheima.service.UserService;

        public class UserServiceImpl implements UserService {
        public void setUserDao(UserDao userDao){
        System.out.println("BeanFactory去调用该方法获得userDao设置到此处:"+userDao);
        }
        }

        //UserDao.java
        package com.itheima.dao;

        public interface UserDao {
        }

        //UserDaoImpl.java
        package com.itheima.dao.impl;

        import com.itheima.dao.UserDao;

        public class UserDaoImpl implements UserDao{
        }

      3. 创建beans.xml配置文件,将UserServiceImpl的信息配置到该xml中。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        <!-- beans.xml -->
        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!-- bean对象:id用于测试代码引用UserServiceImpl类实例,class定位UserServiceImpl类的坐标 -->
        <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
        <!-- DI: bean之间关系:依赖注入 name是指setUserDao方法设置的该类包含的属性名为userDao, ref引用bean对象id为userDao,如此在BeanFactory生成userService Bean对象时BeanFactory会执行setUserDao方法 -->
        <property name="userDao" ref="userDao"></property>
        </bean>
        <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>

        </beans>
      4. 编写测试代码,创建BeanFactory,加载配置文件,获取UserService实例对象。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        package com.itheima.test;

        import org.springframework.beans.factory.support.DefaultListableBeanFactory;
        import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
        //业务代码没有new生成对象,而是IoC转给beanFactory生成bean对象
        public class BeanFactoryTest {
        public static void main(String[] args){
        //创建工厂对象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //创建一个读取器(xml文件)
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        //读取配置文件给工厂
        reader.loadBeanDefinitions("beans.xml");
        //根据id获取Bean示例对象
        Object userService = beanFactory.getBean("userService");
        System.out.println(userService);
        }
        }

      • javaEE三层架构:表现层、业务逻辑层、数据访问层。
        • 表现层:用户交互: 用户输入, 展示数据, 返馈给用户。
        • 业务逻辑层:核心业务,数据的验证、计算、规则执行。
        • 数据访问层:数据交互,增删改查等。
    • ApplicationContext快速入门
      ApplicationContext是Spring容器,内部封装BeanFactory,比BeanFactory功能更丰富,使用ApplicationContext开发时,xml配置文件名称习惯写成applicationContext.xml

    • BeanFactory与ApplicationContext的关系
      1) BeanFactory是Spring早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称为Spring容器。
      2) ApplicationContext在BeanFactory的基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装。
      3) Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以ApplicationContext与BeanFactory既有继承关系又有融合关系。
      4) Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好。

    • BeanFactory的继承体系
      BeanFactory是核心接口,项目运行过程中肯定有具体实现参与,这个具体实现就是DefaultListableBeanFactory, 而ApplicationContext内部维护的BeanFactory的实现类也是它。

    • ApplicationContext的继承体系
      只在Spring基础环境下,即只导入spring-context坐标时,此时的ApplicationContext的继承体系。
      |实现类|功能描述|
      |—-|—-|
      |ClassPathXmlApplicationContext|加载类路径下的xml配置的ApplicationContext|
      |FileSystemXmlApplicationContext|加载磁盘路径下的xml配置的ApplicationContext|
      |AnnotationConfigApplicationContext|加载注解配置类的ApplicationContext|

  • 基于xml的Spring应用

    • SpringBean的配置详解
      Bean的常用配置
      |xml配置方式|功能描述|
      |——————|—-|
      ||Bean的id(转化为beanName)和全限定名配置|
      ||通过name设置Bean的别名(当没有id时name就是beanName),通过别名也能直接获取到Bean实例|
      ||Bean的作用范围,BeanFactory作为容器时取值singleton和prototype|
      ||Bean的实例化时机,是否延迟加载。BeanFactory作为容器时无效|
      ||Bean实例化后自动执行的初始化方法,method指定方法名|
      ||Bean实例销毁前的方法,method指定方法名|
      ||设置自动注入模式,常用的有按照类型b yType,按照名字byName|
      ||指定哪个工厂Bean的哪个方法完成Bean的创建|

      1)Bean的基础配置

      此时存储到Spring容器中的Bean对象的名字beanName(由id经Spring容器转化而成)是userDao,值是 UserDaoImpl对象,可以根据beanName获取Bean实例
      applicationContext.getBean(“userDao”);
      如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
      applicationContext.getBean(“com.itheima.dao.impl.UserDaoImpl”);
      2)Bean的别名配置

      别名aaa或bbb或ccc都可以获取UserDaoImpl实例
      3)Bean的范围设置
      默认情况,单纯的Spring环境Bean的作用范围有两个:Singleton和Prototype

      • singleton: 单例,默认值,Spring容器创建时就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例。
        Object userService = applicationContext.getBean(“userService”);
        Object userService1 = applicationContext.getBean(“userService”);
        userService和userService1的地址一样
      • prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
        Object userService = applicationContext.getBean(“userService”);
        Object userService1 = applicationContext.getBean(“userService”);
        userService和userService1的地址不一样

      4)Bean的延迟加载
      当lazy-init设置为true时为延迟加载,即当Spring容器创建时,不会立即创建Bean实例,等到用到时在创建Bean实例并存储到单例池中,后续在使用该Bean时直接从单例池中获取,本质上该Bean还是单例。

      5) Bean的初始化和销毁配置

      1
      2
      3
      4
      5
      public class UserDaoImpl implements UserDao{
      public UserDaoImpl() {System.out.println("UserDaoImpl创建了...");}
      public void init() {System.out.println("初始化...");}
      public void destroy() {System.out.println("销毁...");}
      }

      注:bean的销毁和bean的销毁方法的调用是两回事,即容器挂掉时bean对象不一定会执行到bean的销毁方法,所以不会调用该方法。
      通过实现InitializingBean接口完成一些Bean的初始化操作

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class UserServiceImpl implements UserService, InitializingBean{
      public UserDaoImpl(){System.out.println("UserServiceImpl创建了...");}
      public void init() {System.out.println("初始化...");}
      public void destroy() {System.out.println("销毁...");}
      //执行时机早于init-method配置的方法
      @Override
      public void afterPropertiesSet() throws Exception{
      System.out.println("InitializingBean...");
      }
      }

      6) Spring实例化配置
      实例化方式

      • 构造方式实例化:底层通过构造方法对Bean进行实例化

        • 无参构造
          1
          2
          <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
          </bean>
          1
          2
          3
          4
          5
          public class UserServiceImpl implements UserService{
          public UserServiceImpl(){
          System.out.println("UserServiceImpl无参实例化");
          }
          }
        • 有参构造
          1
          2
          3
          4
          5
          6
          7
          <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
          <!-- name="name"中"name"是构造函数中的参数名 -->
          <constructor-arg name="name" value="haohao">
          </constructor-arg>
          <constructor-arg name="age" value="37">
          </constructor-arg>
          </bean>
          1
          2
          3
          4
          5
          public class UserServiceImpl implements UserService{
          public UserServiceImpl(String name, int age){
          System.out.println("UserServiceImpl有参实例化");
          }
          }
          注:constructor-arg不光可以用在构造函数的参数,只要是构造bean对象时需要的参数都可以用该标签设置。
      • 工厂方式实例化:底层通过调用自定义工厂方法对其他类的Bean进行实例化

        • 静态工厂方法实例化Bean:静态方法,类名调用

          1
          2
          3
          4
          5
          <!-- 使用factory-method表示生成的Bean对象不是MyBeanFactory1的对象而是用MyBeanFactory1的静态方法userDao生成的UserDao的对象 -->
          <bean id="userDao1" class="com.itheima.factory.MyBeanFactory1" factory-method="userDao">
          <constructor-arg name="name" value="haohao"></constructor-arg>
          <constructor-arg name="age" value="18"></constructor-arg>
          </bean>
          1
          2
          3
          4
          5
          public class MyBeanFactory1 {
          public static UserDao userDao(Stirng name, int age){
          return new UserDaoImpl();
          }
          }
        • 实例工厂方法实例化Bean:非静态方法,实例调用

          1
          2
          3
          4
          5
          6
          7
          <!-- 实例工厂方法首先建一个实例工厂,myBeanFactory2 -->
          <bean id="myBeanFactory2" class="com.itheima.factory.MyBeanFactory2"></bean>
          <!-- 再根据实例工厂factory-bean="myBeanFactory2"调用其工厂方法factory-method="userDao" -->
          <bean id="userDao2" class="com.itheima.factory.MyBeanFactory2" factory-bean="myBeanFactory2" factory-method="userDao">
          <constructor-arg name="name" value="haohao"></constructor-arg>
          <constructor-arg name="age" value="18"></constructor-arg>
          </bean>
          1
          2
          3
          4
          5
          6
          public class MyBeanFactory2 {
          public UserDao userDao(Stirng name, int age){
          System.out.println("MyBeanFactory2.userDao()");
          return new UserDaoImpl();
          }
          }
        • 实现FactoryBean规范延迟实例化Bean
          不需要在指定工厂方法,而是根据规范要求在类中实现接口就可以。
          :生成的bean对象不在singletonObject中,而是在factoryBeanObjectCache中,并且只有getBean时才调用getObject,并存入缓存,下次用时直接从缓存中提取
          image-20250305145439918
          image-20250305145457274

          1
          2
          3
          <!-- 返回的是com.itheima.factory.MyBeanFactory3类中 getObject方法返回的对象 即UserDao对象-->
          <bean id="userDao3" class="com.itheima.factory.MyBeanFactory3">
          </bean>
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          package com.itheima.factory;

          import com.itheima.dao.UserDao;
          import com.itheima.dao.impl.UserDaoImpl;
          import org.springframework.beans.factory.FactoryBean;

          public class MyBeanFactory3 implements FactoryBean<UserDao> {
          @Override
          public UserDao getObject() throws Exception {
          return new UserDaoImpl();
          }

          @Override
          public Class<?> getObjectType() {
          return UserDao.class;
          }
          }

          7) Bean的注入配置和方式:

      • 注入方式
        |注入方式|配置方式|
        | :-: | :-: |
        |通过Bean的set方法注入|
        |
        |通过构造Bean的方法进行注入|
        |

      ref用于注入其他对象引用,引用对应Bean的id, value用于注入普通属性值

      • 依赖注入的数据类型有三种:
        a. 普通数据类型,例如:String, int, boolean等,通过value属性指定。
        b. 引用数据类型,例如:UserDaoImpl, DataSource等,通过ref属性指定。
        c. 集合数据类型,例如:List, Map, Properties, Set等。

        配置文件

        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
         <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
        <!--UserServiceBImpl类生成的userServiceB对象 -->
        <bean id="userServiceB" class="com.itheima.service.impl.UserServiceBImpl">
        <!--UserServiceBImpl类的setStringList方法给stringList赋值 -->
        <property name="stringList">
        <!--UserServiceBImpl类stringList数据的值是一个List类型集合 -->
        <list>
        <!--List集合元素类型是普通类型String,用value赋值 -->
        <value>aaa</value>
        <value>bbb</value>
        <value>ccc</value>
        </list>
        </property>
        <property name="userDaoList">
        <list>
        <!-- 生成bean -->
        <bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
        <bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
        <!-- 引用bean -->
        <ref bean="userDao"></ref>
        <bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
        </list>
        </property>
        <property name="userDaoSet">
        <set>
        <bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
        <bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
        <ref bean="userDao3"></ref>
        <ref bean="userDao2"></ref>
        </set>
        </property>
        <property name="strSet">
        <set>
        <value>111</value>
        <value>222</value>
        <value>333</value>
        </set>
        </property>
        <property name="map">
        <map>
        <entry key="a" value-ref="userDao"/>
        <entry key="b" value-ref="userDao1"/>
        <entry key="c" value-ref="userDao2"/>
        </map>
        </property>
        <property name="properties">
        <props>
        <prop key="p1">ppp1</prop>
        <prop key="p2">ppp2</prop>
        <prop key="p3">ppp3</prop>
        </props>
        </property>
        </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
        package com.itheima.service.impl;

        import com.itheima.service.UserServiceB;
        import com.itheima.dao.UserDao;

        import java.util.List;

        public class UserServiceBImpl implements UserServiceB {
        //注入List
        //普通类型为元素
        private List<String> stringList;
        public void setStringList(List<String> stringList){
        this.stringList = stringList;
        }
        //Bean对象为元素
        private List<UserDao> userDaoList;
        public void setUserDaoList(List<UserDao> userDaoList) {
        this.userDaoList = userDaoList;
        }
        //Set
        private Set<String> strSet;
        public void setStrSet(Set<String> strSet){
        this.strSet = strSet;
        }
        private Set<UserDao> userDaoSet;
        public void setUserDaoSet(Set<UserDao> userDaoSet){
        this.userDaoSet = userDaoSet;
        }
        //Map键值对
        private Map<String, UserDao> map;
        public void setMap(Map<String, UserDao> map){
        this.map = map;
        }
        //Properties 也是一种键值对
        private Properties properties;
        public void setProperties(Properties properties){
        this.properties = properties;
        }
        public void show(){
        System.out.println(stringList);
        System.out.println(userDaoList);
        System.out.println(strSet);
        System.out.println(userDaoSet);
        System.out.println(map);
        System.out.println(properties);
        }
        private UserDao userDao;

        //BeanFactory去调用该方法,从容器中获得userDao设置到此处
        public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
        }
        }

        接口

        1
        2
        3
        4
        5
        package com.itheima.service;

        public interface UserServiceB {
        public void show();
        }

        测试代码

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        package com.itheima.test;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.support.ClassPathXmlApplicationContext;
        import com.itheima.dao.UserDao;
        import com.itheima.service.UserService;
        import com.itheima.service.UserServiceB;

        public class ApplicationContextTest {
        public static void main(String[] args){
        // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        /* Object userService = applicationContext.getBean("userService");
        System.out.println(userService);
        Object userDao3 = applicationContext.getBean("userDao3");
        System.out.println(userDao3);
        UserDao userDao1 = (UserDao) applicationContext.getBean("userDao1");
        UserDao userDao2 = (UserDao) applicationContext.getBean("userDao2");
        */
        UserServiceB userServiceB = (UserServiceB) applicationContext.getBean("userServiceB");
        userServiceB.show();
        applicationContext.close(); //关闭容器,销��Bean
        }
        }

        输出

        1
        2
        3
        4
        5
        6
        [aaa, bbb, ccc]
        [com.itheima.dao.impl.UserDaoImpl@6dc17b83, com.itheima.dao.impl.UserDaoImpl@5e0826e7, com.itheima.dao.impl.UserDaoImpl@6fe7aac8, com.itheima.dao.impl.UserDaoImpl@32eff876]
        [111, 222, 333]
        [com.itheima.dao.impl.UserDaoImpl@31368b99, com.itheima.dao.impl.UserDaoImpl@1725dc0f, com.itheima.dao.impl.UserDaoImpl@3911c2a7, com.itheima.dao.impl.UserDaoImpl@4ac3c60d]
        {a=com.itheima.dao.impl.UserDaoImpl@6fe7aac8, b=com.itheima.dao.impl.UserDaoImpl@8dbdac1, c=com.itheima.dao.impl.UserDaoImpl@4ac3c60d}
        {p1=ppp1, p2=ppp2, p3=ppp3}
      • 自动装配方式
        如果被注入的属性类型是Bean引用的话,那么可以在标签中使用autowire属性去配置自动注入方式,属性值有两个:
        • byName: 通过属性名自动装配,即去匹配setXxx与其他bean对象中的id=”xxx”或name=”xxx”是否一致,匹配上就注入其他对象成功,否则失败。
        • byType: 通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时报错。
          1
          <bean id="userServiceB" class="com.itheima.service.impl.UserServiceBImpl" autowire="byType"/>
          8)默认标签
          Spring的xml标签大体上分为两类,一类是默认标签,一类是自定义标签
      • 默认标签:不用额外导入其他命名空间约束的标签,例如标签
      • 自定义标签:需要额外引入其他命名空间约束,并通过前缀引用的标签,例如标签
    • Spring的get方法

    • Spring配置非自定义Bean

    • Bean实例化的基本流程

    • Spring的后处理器

    • Spring Bean的生命周期

    • Spring IoC整体流程总结

    • Spring xml方式整合第三方框架

  • 基于注解的Spring应用