Spring原理

  • 需要Markdown格式的文件或者三级缓存详细流程可私聊作者。
  • 有错误请各位大佬指出。

Spring诞生

JavaBean和SpringBean

JavaBean

一种特殊的类 ,可以理解为就是一种POJO,具有一下特征

  • 提供一个默认的无参构造函数。
  • 需要被序列化并且实现了 Serializable 接口。
  • 可能有一系列可读写属性,并且一般是 private 的。
  • 可能有一系列的 getter 或 setter 方法。
SpringBean
  • SpringBean和对象很相似,可以理解为,由Spring管理的对象就称之为Bean

IoC DI AOP

  • IoC:控制反转,强调的是在原来在程序中创建Bean的权利反转给第三方
  • DI:依赖注入 强调Bean之间的关系,由第三方负责设置
  • AOP:对某一个Bean进行增强

Spring简介

在这里插入图片描述

BeanFactory

  • BeanFactory :最底层最核心 本身是个Bean工厂

  • 在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">
            <!--  配置UserServiceImpl-->
        <bean id="userService" class="org.example.service.Impl.UserServiceImpl">
            <!--property 注入配置-->
            <!--name UserServiceImpl中set方法的名称 ref设置bean.xml中的-->
            <property name="userDao" ref="userDao"/>
        </bean>
            <!--  配置UserDaoImpl-->
        <bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
    </beans>
    
  • UserService

    public class UserServiceImpl implements UserService {
        private UserDao userDao;
        //BeanFactory调用该方法 从容器中获得userDao 设置到此处
        public void setUserDao(UserDao userDao){
            System.out.println("BeanFactory调用该方法从容器中获得userDao设置到此处"+userDao);
            this.userDao=userDao;
        }
    }
    
  • 打印测试

            //创建工厂对象
            DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
            //创建读取器(xml文件)
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
            //读取配置文件给工厂
            reader.loadBeanDefinitions("beans.xml");
            //根据id获取bean实例对象
            UserService userService =(UserService) beanFactory.getBean("userService");
    
  • 结果

    在这里插入图片描述

ApplicationContext

  • ApplicationContext :Spring容器,内部封装了BeanFactory 使用ApplicationContext开发,xml文件习惯写成ApplicationContext.xml

  • 打印测试

    	ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService =(UserService) context.getBean("userService");
        System.out.println(userService);
    
  • 结果
    在这里插入图片描述

BeanFactory和ApplicationContext关系

  • BeanFactory是核心接口 ,最直接的实现是DefaultListableBeanFactory

  • 在spring-context(基础环境)中的环境ApplicationContext继承体系

    在这里插入图片描述

  • BeanFactory 是Spring早期接口,称为Spring工厂 ,ApplicationContext是后期更高级的接口,称之为Spring容器

  • ApplicationContext对BeanFactory 进行了扩展 ,BeanFactory 是更加底层的东西,ApplicationContext是对其API进行了封装

    在这里插入图片描述

  • Bean创建的主要逻辑功能都被封装在BeanFactory中,ApplicationContext继承了BeanFactorty,又有融合关系

  • Bean的初始化时机不同,原始的BeanFactory是在首次调用getBean时才进行Bean的创建,ApplicationContext是配置文件加载的,容器一创建就将Bean都实例化并初始化好,简单说BeanFactory是延迟的,ApplicationContext是立即的

  • ApplicationContext内部维护着BeanFactory的引用

在这里插入图片描述

基于xml的Spring应用

Spring的配置标签详解

Bean常用配置

在这里插入图片描述

bean基本配置
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
  • id 会自动转换成name
  • 不配置id的话 ,Bean依然会有name ,Bean的name为 class 的值:全限定名
Bean的别名(一般不用)
<bean id="userDao" name="aaa,bbb" class="org.example.dao.impl.UserDaoImpl"/>
  • 别名会单独存储,Bean的name和别名是两种东西
  • id不配置时,。Bean的name是别名的第一个
Bean的范围配置(scope)
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl" scope="prototype"/>
  • 在Spring基本环境中
    • singleton(默认):单例模式,在创建Spring容器的时候加载完就创建出Bean,使用后不会进行销毁
    • prototype :原型模式,在创建Spring容器后的时候不会创建Bean ,当执行getBean的时候会创建Bean,使用后会进行销毁
  • 在MVC环境中会不同
Bean的延迟加载(lazy-init)
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl" lazy-init="true"/>
  • 设置为true为延迟加载,使用BeanFactory是无效的
  • 在使用getBean的时候才会创建,可以减少内存占用
Bean的初始化和销毁方法配置
  • xml配置
<bean id="userService" class="org.example.service.Impl.UserServiceImpl" init-method="init" destroy-method="destroy"/>
  • userService中配置
public void init(){
    System.out.println("初始化方法");
}
public void destroy(){
    System.out.println("销毁方法");
}
  • 没有显式的关闭,不会调用销毁方法

拓展:

  • 实现InitializingBean接口,初始化Bean
public class UserServiceImpl implements UserService , InitializingBean {
    public void init(){
        System.out.println("初始化方法");
    }
    public void destroy(){
        System.out.println("销毁方法");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet执行");
    }
}
  • afterPropertiesSet会在初始化方法init前执行
  • afterPropertiesSet会在依赖注入之后进行调用
Bean的实例化配置
  • 构造方式实例化:通过构造方法对Bean进行实例化

    • 无参构造(Bean只有基本配置的时候)

    • 有参构造

      xml配置(constructor-arg 标签 name:参数的名称 value:参数的值)

      <bean id="userService" class="org.example.service.Impl.UserServiceImpl">
          <!--property 注入配置-->
          <!--name set方法的名称 ref设置bean.xml中的-->
          <property name="userDao" ref="userDao"/>
          <constructor-arg name="name" value="hello"/>
          <constructor-arg name="age" value="18"/>
      </bean>
      
  • 工厂方式实例化:通过调用自定义的工厂方法对Bean进行实例化

    • 静态工厂方法

      1. 静态方法
      public class MyBeanFactory1 {
          public static UserDao userDao(){
              return new UserDaoImpl();
          }
      }
      
      1. xml配置
      <bean id="userDao" class="org.example.factory.MyBeanFactory1" factory-method="userDao"/>
      

      作用:Bean在创建之前可以进行其他的业务逻辑操作,引用其他使用jar包的Bean的时候使用

    • 实例工厂方法

      1. 方法配置
      public class MyBeanFactory2 {
          public UserDao userDao(){
              return new UserDaoImpl();
          }
      }
      
      1. xml配置
      <!--配置工厂对象-->
      <bean id="myBeanFactory2" class="org.example.factory.MyBeanFactory2" />
      <!--创建Bean,使用创建的对象去调用方法-->
      <bean id="userDao2" factory-bean="myBeanFactory2" factory-method="userDao"/>
      

      作用:第三方Bean产生,不是通过静态方法产生,是使用方法产生

    • 带参数的工厂方法(constructor-arg)

      方法

      public class MyBeanFactory1 {
          public static UserDao userDao(String name,int age){
              return new UserDaoImpl();
          }
      }
      

      xml配置

      <!--  配置UserDaoImpl-->
      <bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
      <bean id="userDao1" class="org.example.factory.MyBeanFactory1" factory-method="userDao">
          <constructor-arg name="name" value="hello"/>
          <constructor-arg name="age" value="18"/>
      </bean>
      
    • 实现FactoryBean规范延迟实例化Bean

      FactoryBean实现

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

      xml配置

      <bean id="userDao3" class="org.example.factory.MyBeanFactory3"/>
      

      特点:

      创建Spring容器的时候不会创建Bean,只会创建实现了FactoryBean的工厂,只有在getBean的时候才会创建出Bean,并且将Bean缓存到factoryBeanObjectCache中,再次getBean这个Bean的时候也不会再创建,而是会从缓存中寻找这个Bean

      好处:

      临时把产生Bean的需求产生,只有在你获取Bean的时候才会创建

Bean的注入方式

两种方式

在这里插入图片描述

Bean的依赖注入配置

​ Bean中需要有set方法才能注入

​ 对于引用类型使用对应的ref项,普通数据使用value

  • 普通数据类型:String,int,oolean

    <property name="string" value="aa"/>
    
  • 引用数据类型:UserDao,DataSouce

    <property name="string" ref="aa"/>
    
  • 集合数据类型:List,Map

    xml配置

    <bean id="userService" class="org.example.service.Impl.UserServiceImpl">
        	<-- list -->
            <property name="stringList">
                <list>
                    <value>aaa</value>
                    <value>bbb</value>
                    <value>ccc</value>
                </list>
            </property>
            <property name="userDaoList">
                <list>
                    <!--每一个Bean都会创建不同的对象-->
                    <bean class="org.example.dao.impl.UserDaoImpl"/>
                    <bean class="org.example.dao.impl.UserDaoImpl"/>
                    <bean class="org.example.dao.impl.UserDaoImpl"/>
                    <!--引用外部的Bean-->
                    <ref bean="userDao1"/>
                    <ref bean="userDao2"/>
                    <ref bean="userDao3"/>
                </list>
            </property>
        </bean>
        	<-- map-->
            <property name="userDaoMap">
                <map>
                    <entry key-ref="userDao" value="aaa"/>
                    <entry key-ref="userDao1" value="aaa"/>
                    <entry key-ref="userDao2" value="aaa"/>
                </map>
            </property>
            <--properties-->
            <property name="properties">
                <props>
                    <prop key="p1">prop1</prop>
                    <prop key="p2">prop2</prop>
                    <prop key="p3">prop3</prop>
                </props>
            </property>
    
  • 自动装配

    • 使用autowrie属性配置自动注入:

      ByName:通过属性名自动装配 ,匹配setXxx与id =“xxx”(name=“xxx”)是否一致

      <bean id="userService" class="org.example.service.Impl.UserServiceImpl" autowire="byName">
      

      byType:通过Bean的类型从容器中匹配,匹配出多个相同的Bean类型时,报错

      <bean id="userService" class="org.example.service.Impl.UserServiceImpl" autowire="byType">
      

Spring的其他配置标签

  • 默认标签:不用额外导入其他命名空间约束的标签

    在这里插入图片描述

  • 自定义标签:需要额外引入其他命名空间约束

    xmlns:默认命名空间

    xmlns:xxx:引入空间 xxx为使用时的前缀

    xsi:schemaLocation:具体文件位置,引入的时候需要引入两份

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:contxt="http://www.springframework.org/schema/context"
           <-- 使用Spring的可以直接copy然后改后缀-->
    	   <--其他的需要自己引入-->
           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
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
        <--自定义标签使用-->
        <contxt:annotation-config/>
    
Beans标签
  • 区分不同环境—profile

    不指定环境只会生效外部通用的部分

    指定环境,环境内部的和外部通用的都会生效

    <?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">
    	<beans profile="dev">
        	<bean id="UserService" class="org.example.service.Impl.UserServiceImpl"/>
    	</beans>
    	<beans profile="test">
        	<bean id="UserService" class="org.example.service.Impl.UserServiceImpl"/>
    	</beans>
    </beans>
    
    //指定环境
    System.setProperty("spring.profiles.active","dev");
    
Import标签
  • 引入其他配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:contxt="http://www.springframework.org/schema/context"
           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
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-beans.xsd">
        <!--通过import引入其他模块的配置文件-->
        <import resource="applicationContext-user.xml"/>
        <import resource="applicationContext-order.xml"/>
    </beans>
    
alias标签
  • 起别名 和Bean中用name效果一样

    <bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
    <alias name="userDao" alias="xxx"/>
    <alias name="userDao" alias="yyy"/>
    <alias name="userDao" alias="zzz"/>
    

​ 别名会在aliasMap中维护

Spring的get方法

获取容器中的Bean

在这里插入图片描述

UserService userService =(UserService) context.getBean("userService");
UserService userService1 = context.getBean("userService", UserService.class);
UserService userService2 = context.getBean(UserService.class);

Spring 配置非自定义Bean

  1. 考虑两个问题

    • 配置Bean的实例化方式用什么?

      • 无参构造
      • 有参构造
      • 静态工厂
      • 实例工厂
    • 被配置的Bean是否需要注入必要的属性

  2. 配置Druid数据源交给Spring管理

    • 导入坐标

      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.20</version>
      </dependency>
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
      </dependency>
      

      DruidDataSource具有无参构造 可以交由Spring管理——使用无参构造的方式实例化

      DruidDataSource需要配置参数,都是普通数据类型,CLassName、url、username、password

    • 在xml中进行配置

       <!--配置数据源信息-->
          <bean id ="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/mp"/>
              <property name="username" value="root"/>
              <property name="password" value="123456"/>
          </bean>
      
  3. 配置Connection交由Spring管理

    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection connection = DriverManager.getConnection("", "", "");
    

    Connection对象的产生是由静态方法产生——使用静态工厂实例化

    • 在xml中配置

       <!--配置Connection-->
          <!--Class.forName("com.mysql.cj.jdbc.Driver");-->
          <bean id="class" class="java.lang.Class" factory-method="forName">
              <constructor-arg name="className" value="com.mysql.cj.jdbc.Driver"/>
          </bean>
          <!--Connection connection = DriverManager.getConnection("", "", "");-->
          <bean id="connection" class="java.sql.DriverManager" factory-method="getConnection" scope="prototype">
              <constructor-arg name="url" value="jdbc:mysql://localhost:3306/mp"/>
              <constructor-arg name="user" value="root"/>
              <constructor-arg name="password" value="123456"/>
          </bean>
      
  4. 配置日期对象交给Spring管理

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MMM-dd");
    Data data = simpleDateFormat.parse("2023-08-27 :12:00:00");
    

    data对象的产生是由simpleDateFormat的方法产生的——使用实例化工厂的方式

    • 在xml中配置
    <!--配置日期对象-->
    <bean id = "simpleDateFormat" class="java.text.SimpleDateFormat">
        <constructor-arg name="pattern" value="yyyy-MM-dd"/>
    </bean>
    <!--配置Date对象-->
    <bean id="date" class="java.util.Date" factory-bean="simpleDateFormat" factory-method="parse">
        <constructor-arg name="source" value="2023-08-27 :12:00:00"/>
    </bean>
    
  5. 配置Mybatis的SqlSessionFactory交由Spring管理

    • 导入坐标

      <!--mybatis-->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.14</version>
      </dependency>
      
    • 自己产生

      //静态工厂方法方式
      InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
      //无参构造实例化
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      //实例工厂方法
      SqlSessionFactory sqlSessionFactory = builder.build(in);
      
    • xml配置

      <!--配置输入流对象-->
      <bean id="in" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
          <constructor-arg name="resource" value="mybatis-config.xml"/>
      </bean>
      <!--配置SqlSessionFactoryBuilder对象-->
      <bean id="builder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"/>
      <!--实例工厂方法-->
      <bean id="sqlSessionFactory" factory-bean="builder" factory-method="build">
          <constructor-arg name="inputStream" ref="in"/>
      </bean>
      

Bean实例化的基本流程

  • Spring容器在初始化的时候,会将xml配置的**< Bean >的信息封装到一个BeanDefinition对象**(不是将xml配置的bean对象本身封装),

  • 所有的BeanDefinition存储到一个beanDefinitionMap的Map集合中,配置Bean有依赖注入的时候会将信息存在beanDefinitionMap中的propertyValues

  • Spring再对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储到一个名为singletonObjects的Map集合中(存储到单例池

  • 当调用getBean(beanName)方法时,从该Map集合中匹配Bean实例对象返回

    在这里插入图片描述

Spring后处理器 ***

  • Spring后处理器是Spring对外开发的重要扩展点(允许我们介入到Bean的整个实例化流程中)动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用

  • BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕之前,Bean实例化之前执行

  • BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池之前执行

Bean工厂后处理器—BeanFactoryPostProcessor

  • BeanFactoryPostProcessor是一个接口规范,实现了该接口的类配置给Spring容器管理之后,Spring就会回调该接口的方法,对BeanDefinition进行注册修改

  • BeanFactoryPostProcessor主要是对Bean定义的操作

    修改已有的Bean

    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            System.out.println("beanDefinitionMap填充完毕后回调该方法");
            BeanDefinition userService = beanFactory.getBeanDefinition("userService");
            //修改实例化的类
            userService.setBeanClassName("org.example.dao.impl.UserDaoImpl");
        }
    }
    

    注册Bean

    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            //注册BeanDefinition
            BeanDefinition definition = new RootBeanDefinition();
            //设置实例化的类
            definition.setBeanClassName("org.example.dao.impl.PersonDaoImpl");
            //强转为DefaultListableBeanFactory
            DefaultListableBeanFactory defaultListableBeanFactory= (DefaultListableBeanFactory) beanFactory;
            //注册
            defaultListableBeanFactory.registerBeanDefinition("personDao",definition);
        }
    }
    
    • 使用BeanDefinitionRegistryPostProcessor进行注册

      public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
      
          @Override
          public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
              //注册BeanDefinition
              BeanDefinition definition =new RootBeanDefinition();
              definition.setBeanClassName("org.example.dao.impl.PersonDaoImpl");
              registry.registerBeanDefinition("personDao",definition);
          }
      
          @Override
          public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      
          }
      }
      

    在这里插入图片描述

注解注册Bean原理

  • 定义注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyComponent {
        String value();
    }
    
  • 定义注册Bean的类

    public class MyComponentBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            //通过扫描工具去扫描指定包及其包下的所有类 收集使用@Mycomponent的类
            Map<String, Class> stringClassMap = BaseClassScanUtils.scanMyComponentAnnotation("org.example");
            //遍历Map组装BeanDefinition进行注册
            stringClassMap.forEach((beanName,clazz)->{
                //获得beanClassName
                String beanClassName = clazz.getName();
                //创建BeanDefinition
                RootBeanDefinition beanDefinition = new RootBeanDefinition();
                beanDefinition.setBeanClassName(beanClassName);
                //注册
                registry.registerBeanDefinition(beanName,beanDefinition);
            });
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
        }
    }
    
  • 需要注册的Bean加入注解

    @MyComponent("otherBean")
    public class OtherBean {
    }
    

Bean后处理器—BeanPostProcessor

  • Bean实例化后,缓存到singletonObjects单例池,中间会经过Bean的初始化过程

  • BeanPostProcessor:对Bean对象的操作

    public class MyBeanPostProcessor implements BeanPostProcessor {
        //先执行
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println(beanName+":postProcessAfterInitialization");
            return bean;
        }
        /*
        中间会经理一系列方法、init初始化等等
        */
        //后执行
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println(beanName+":postProcessBeforeInitialization");
            //返回的是需要的Bean
            return bean;
        }
    }
    

使用BeanPostProcessor对Bean进行增强

  • IOC对Bean进行增强返回proxy

    public class TimeLogBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            //使用动态代理对目标Bean进行增强,返回proxy对象,进而存储到单例池
           Object beanProxy =  Proxy.newProxyInstance(
                    bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (proxy, method, args) -> {
                        //1.输出开始时间
                        System.out.println("方法"+method.getName()+"-开始时间:"+new Date());
                        //2.执行目标时间
                        Object result = method.invoke(bean, args);
                        //3.输出结束时间
                        System.out.println("方法"+method.getName()+"-结束时间:"+new Date());
                        return result;
                    }
            );
            return beanProxy;
        }
    }
    

两种后处理器的流程 ***

  • 流程图示

    在这里插入图片描述

SpringBean的生命周期

SpringBean的生命周期是从Bean实例化之后,即通过反射创建出对象之后,到Bean成为一个完整的对象,最终存储到单例池中过程

  • Bean的实例化阶段:Spring框架会阙处BeanDefinition的信息进行判断当前的Bean的范围是否是singleton的,是否不是延迟加在的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化
  • Bean的初始化阶段:Bean创建之后还仅仅是个半成品,还需要对Bean的实例的属性进行填充、执行一些Aware接口方法,执行BeanPostProcessor(Bean后处理器)方法、执行InitializingBean接口的初始化方法、执行自定义初始化ini方法等。该阶段是最具技术含量和复杂度的阶段,AOP增强功能,注解功能都在这里体现
  • Bean的完成阶段:经过初始化阶段,Bean成为了一个完整的SpringBean,被存储到单例池,完成整个周期

Bean的初始化阶段

  • 流程
    • Bean实例的属性填充
    • Aware接口属性注入
    • BeanPostProcessor的befor()方法回调
    • InitializingBean接口的初始化方法回调
    • 自定义初始化方法init回调
    • BeanPostProcessor(Bean后处理器)的after()方法的回调

Bean属性填充

  • 在BeanDefiniton中对Bean实体的注入信息通过属性propertyValues进行存储

    在这里插入图片描述

    在这里插入图片描述

  • 属性注入的三种情况

    • 普通属性:直接通过set方法的反射设置进去
    • 单向对象引用:从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建注入对象Bean实例(完成整个生命周期)后,进行注入
    • 双向对象引用循环依赖
  • 双向注入:循环引用问题

    在这里插入图片描述

  • 三级缓存

    Spring提供三级缓存存储完成的Bean实例和半成品的Bean实例,解决循环引用问题

    DefaultSingletonBeanRegistry中提供了三个Map

    • singletonObjects:一级缓存
    • earlySingletonObjects:二级缓存 存储已经被引用的Bean,从三级缓存中移除,然后存储到二级缓存
    • singletonFactories:三级缓存 存储的是刚实例化的Bean,未被引用的Bean但是还没有开始初始化,通过ObjectFactory把刚实例化的Bean包装成ObjectFactory,在需要的时候通过getObject()方法进行返回
    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
        //最终存储单例Bean成品的容器,即实例化和初始化都完成的bean ————————  一级缓存
        private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
        
        //早期的Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了 ———————— 二级缓存
        private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
        
        //单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean ———————— 三级缓存
        private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    }
    

    循环依赖过程描述

    <bean id="userDao" class="org.example.dao.impl.UserDaoImpl">
        <property name="userService" ref="userService"/>
    </bean>
    <bean id="userService" class="org.example.service.Impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
    
    1. UserService实例化对象,但尚未初始化,将UserService存储到三级缓存中
    2. UserService属性注入,需要UserDao,从缓存中获取,没有UserDao
    3. UserDao实例化对象,但尚未初始化,将UserDao存储到三级缓存
    4. UserDao属性注入,需要UserService,从三级缓存中获取UserService,UserService从三级缓存移入二级缓存
    5. UserDao执行其他生命周期,最终完成一个Bean,存储到一级缓存中,删除二级三级缓存(其实二级缓存中没有数据,但是还是会同时删除二级和三级缓存),回溯
    6. UserService注入UserDao
    7. UserService执行其他生命周期过程,最终完成一个Bean,存储到一级缓存,删除二级三级缓存

    在这里插入图片描述

常用Aware接口

  • Aware接口是一种框架辅助属性注入的思想,其他框架中也可以看到类似的接口。框架具备高度密封性,我们接触的一般都是业务代码,底层功能API不能轻易获取,使用这些对象的时候就可以使用Aware的接口,让框架注入该对象

    在这里插入图片描述

IoC整体流程

在这里插入图片描述

Spring xml方式整合第三方框架

  • 不需要自定义命名空间,不选需要使用Spring的配置文件配置第三方框架本身,例如Mybatis

  • 需要引入第三方框架的命名空间,需要使用Spring的配置文件配置第三方框架本身,例如Dubbo

    <beans xmlns="http://www.springframework.org/schema/beans"
           <--自定义命名空间-->
           xmlns:contxt="http://www.springframework.org/schema/context"
           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
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    

整合Mybaits

  • 原理

    主要涉及四个类

    • SqlSessionFactoryBean:需要进行配置,用于提供SqlSessionFactory
    • MapperScannerConfigurer:需要进行配置,用于扫描指定mapper注册BeanDefinition
    • MapperFactoryBean:Mapper的FacatoryBean,获得指定Mapper时调用getObnject
    • ClassPathMapperScanner:definition.setAutowrieMode(2) 修改自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入
  • 原始写法

    //获取配置文件
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    //获得SqlSessionFactoryBuilder对象
    SqlSessionFactoryBuilder builder =new SqlSessionFactoryBuilder();
    //创建sqlSessionFactory工厂
    SqlSessionFactory sqlSessionFactory = builder.build(in);
    //获得sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //最后拿到对应的mapper
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //获得数据
    List<User> userList = mapper.findAll();
    for (User user : userList) {
        System.out.println(user.toString());
    }
    

    Spring帮我们做前面的事,我们只需要获得最后的mapper

  • xml配置Mybatis的两个配置类,以及数据源

     <!--配置数据源信息-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/ems"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </bean>
    <!--配置SqlSessionFactoryBean,作用:将SqlSessionFactory存储到Spring容器-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置MapperScannerConfigurer,作用:产生Mapper对象存储到Spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="org.example.mapper"/>
    </bean>
    
  • 注入所需mapper

    //需要Mapper
    private UserMapper userMapper;
    
    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
    
  • xml配置

    <bean id="userService" class="org.example.service.Impl.UserServiceImpl">
        <property name="userMapper" ref="userMapper"/>
    </bean>
    

整合Context

  • xml配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           <-- 配置命名空间-->
           xmlns:contxt="http://www.springframework.org/schema/context"
           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
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
        <!--加载properties文件-->
        <contxt:property-placeholder location="classpath:jdbc.properties"/>
        <!--配置数据源信息-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    

自定义命名空间

  • 自定义标签的约束与物理约束与网络约束名称的约束以键值对形式存储到spring.schemas文件中,该文件储存在类加载路径的META-INF里,Spring会自动加载到
  • 自定义命名空间的名称与自定义命名空间的处理映射关系以键值对的形式存在一个名叫spring.handlers文件中,该文件储存在类加载路径的META-INF里,Spring会自动加载到

基于注解的Spring应用

Bean基本注解开发

​ 需要配置包扫描

<!--注解扫描:扫描指定的基本包及其子包下的类,识别使用@Component-->
<context:component-scan base-package="org.example"/>

Bean标签注解的替代

在这里插入图片描述
在这里插入图片描述

Component衍生注解

@Repostory—@Service—@Controller

image-20240523161358037

Bean依赖注入开发

在这里插入图片描述

@Value

  • @Value使用

    一般用于读取配置

        //    @Value("admin")
        @Value("${jdbc.username}")
        private String username;
    
    	//    @Value("zhang")
        public void setUsername(String username) {
            this.username = username;
        }
    

@Autowired

  • @Autowried使用

    先根据Bean类型进行匹配,再根据名称进行匹配

        //根据类型注入,如果同一类型的Bean有多个,再尝试根据名字进行二次匹配,匹配不成功再报错
    	// @Autowired
        private UserMapper userMapper;
        @Autowired
        public void setUserMapper(UserMapper userMapper) {
            this.userMapper = userMapper;
        }
    

    拓展:

    @Autowired
    public void xxx (UserMapper userMapper){
        System.out.println("xxx:"+userMapper);
    }
    //会将所有的UserMapper放在集合之中
    @Autowired
    public void yyy(List<UserMapper> userMappers){
        System.out.println("yyy:"+userMappers);
    }
    

@Qualifier

  • @Qualifter使用

    配合Autowired一起使用

    //根据类型注入
    @Autowired
    //结合Autowired一起使用,指定按照Bean的名称进行匹配
    @Qualifier("userMapper")
    private UserMapper userMapper;
    

@Rescoure

  • @Rescoure使用

    不加参数根据类型注入加参数根据名称

    //不指定名称参数时根据类型注入,指定名称就根据名称注入
    @Resource(name="userMapper")
    private UserMapper userMapper;
    

非自定义Bean注解开发

  • 非自定义的Bean不能像自定义的Bean一样使用@Component进行管理,非自定义Bean要通过工厂的方式进行实例化

@Bean

@Component
public class OtherBean {
    //不写参数,默认名为类的名字或方法名
    @Bean("dataSource")
    public DataSource dataSource(
        	// 注入普通属性
            @Value("${jdbc.driver}")String driverClassName,
            @Autowired UserMapper userMapper,
        	// 按照名称注入
            @Qualifier("userMapper2") UserMapper userMapper2,
            //按照类型注入可以省略
            UserService userService
            ){
        DruidDataSource dataSource =new DruidDataSource();
        //设置4个资本参数 ....
        dataSource.setDriverClassName(driverClassName);
        return dataSource;
    }
}

Bean配置类的注解开发

@Configuration—@ComponentScan—@PropertySource—@Import

  • 使用配置类替代原有的配置文件

    • @Configuration

      标注当前类为配置类,替代原有的xml文件

    • @ComponentScan

      组件扫描配置,替代xml中<context:component-scan base-package=“org.example”/>

      不配置包名扫描@ComponentScan注解配置类所在包及其子包下的类

    • @PropertySource

      加载外部properties资源配置,替代xml中的//<context:property-placeholder location=“jdbc.properties”/>

    • @Import

      加载其他配置类

    //标注当前类是一个配置类(替代配置文件)
    //@Component
    @Configuration
    //包扫描
    //<context:component-scan base-package="org.example"/>
    @ComponentScan("org.example")
    //多个包
    //@ComponentScan({"org.example","xxx","yyy"})
    //加载properties文件
    //<context:property-placeholder location="jdbc.properties"/>
    @PropertySource("classpath:jdbc.properties")
    //多个配置文件
    //@PropertySource({"classpath:jdbc.properties","xxx","yyy"})
    // <import resource=""/>
    @Import(OtherBean.class)
    public class SpringConfig {
    
    }
    
  • 创建Spring容器

    	//注解方式创建Spring容器
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
       //xml方式创建Spring容器
       ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    
  • 引入外部的配置文件

    // <import resource=""/>
    @Import(OtherBean.class)
    

Spring配置其他注解

@Primary

  • 用于标注相同类型的Bean优先被使用权
  • 与@Component和@Bean一起使用,标注该Bean的优先级更高

@Profile

  • 标注再类或者方法上,标注当前产生的Bean属于那个环境,只有激活了当前环境,被标注的Bean才能被注册到Spring中,不指定环境的Bean,任何环境下都能注册到Spring容器中

    //切换环境
    System.setProperty("spring.profiles.active","test");
    

Spring注解的解析原理

  • 注解是在Bean后处理工厂对Bean进行一个操作的

在这里插入图片描述

Spring注解整合第三方框架

  • 配置代码

    //标注当前类是一个配置类(替代配置文件)
    //@Component
    @Configuration
    //包扫描
    //<context:component-scan base-package="org.example"/>
    @ComponentScan("org.example")
    //多个包
    //@ComponentScan({"org.example","xxx","yyy"})
    //加载properties文件
    //<context:property-placeholder location="jdbc.properties"/>
    @PropertySource("classpath:jdbc.properties")
    //多个配置文件
    //@PropertySource({"classpath:jdbc.properties","xxx","yyy"})
    //组件扫描
    @MapperScan("org.example.mapper")
    public class SpringConfig {
        @Bean
        public DataSource dataSource(@Value("${jdbc.driver}")String driverClassName,
                                     @Value("${jdbc.url}") String url,
                                     @Value("${jdbc.username}") String username,
                                     @Value("${jdbc.password}") String password){
            DruidDataSource dataSource =new DruidDataSource();
            dataSource.setDriverClassName(driverClassName);
            dataSource.setUrl(url);
            dataSource.setPassword(password);
            dataSource.setUsername(username);
            return dataSource;
        }
        @Bean
        public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
            SqlSessionFactoryBean sqlSessionFactoryBean =new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSource);
            return sqlSessionFactoryBean;
        }
    }
    

@Import

  • 可以导入一下三种类

    • 普通配置类

    • 实现Importselector接口的类—注册第三方的Bean

      public class MyImportSelector implements ImportSelector {
          @Override
          public String[] selectImports(AnnotationMetadata annotationMetadata) {
              //参数annotationMetadata叫做注解媒体数组,该对象分装的是当前使用了@Import注解的类上的其他注解的元信息
              Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName());
              String[] basePackages = (String[]) annotationAttributes.get("basePackages");
              System.out.println(basePackages[0]);
              /*annotationAttributes.forEach((attrName,attrValue)->{
                  System.out.println(attrName+"=="+attrValue);
              });*/
              //返回的数组是需要被注册到Spring容器中的Bean的全限定名
              return new String[]{OtherBean2.class.getName()};
          }
      }
      
    • 实现ImportBeanDefinitionRegistrar接口的类—注册第三方的Bean

      public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
          @Override
          public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
              //注册BeanDefinition
              BeanDefinition beanDefinition =new RootBeanDefinition();
              beanDefinition.setBeanClassName(OtherBean2.class.getName());
              registry.registerBeanDefinition("otherBean2",beanDefinition);
          }
      }
      

      封装自己的注解

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Import(MyImportBeanDefinitionRegistrar.class)
      public @interface MyImport {
      }
      

      直接使用@MyImport

AOP

AOP简介

概念

  • 面向切面编程,面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等等。

  • AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,用这种思维去设计编程的方式叫做面向切面编程

  • 对Bean本身的增强

在这里插入图片描述

实现方案

  • 动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时嵌入执行其他增强逻辑或其他对象的方法

    在这里插入图片描述

AOP代码模拟

  • AOP代码模拟

    public class MockAopBeanPostProcessor implements BeanPostProcessor ,ApplicationContextAware{
        private ApplicationContext context;
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            //目的:对UserService中的show1和show2进行增强,增强的方法存在MyAdvice
            //问题: 1.筛选service.impl包下的所有类进行增强,解决方案if判断
            //      2.MyAdvice怎么获取 解决方案:从Spring容器中获取MyAdvice
            if (bean.getClass().getPackage().getName().equals("org.example.service.impl")) {
                //生成当前Bean的Proxy
               Object beanProxy =  Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(),
                        (proxy, method, args) -> {
                            MyAdvice myAdvice = context.getBean(MyAdvice.class);
                            //执行增强对象的before方法
                            myAdvice.afterAdvice();
                            //执行目标对象的目标方法
                            Object result = method.invoke(bean, args);
                            //执行增强对象的after方法
                            myAdvice.afterAdvice();
                            return result;
                        });
                return beanProxy;
            }
            return bean;
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.context =applicationContext;
        }
    }
    
  • 需要增强的代码

    //增强类,内部提供增强方法
    public class MyAdvice {
        public void beforeAdvice(){
            System.out.println("前置增强");
        }
        public void afterAdvice(){
            System.out.println("后置增强");
        }
    }
    

AOP相关概念

在这里插入图片描述

xml配置AOP

基本使用

  • 导入坐标

    <!--AOP融入了aspectj的特性-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.19</version>
    </dependency>
    
  • 配置目标类、配置通知类

    <!--配置通知类-->
    <bean id="advice" class="org.example.advice.MyAdvice"/>
    <!--配置目标类-->
    <bean id="userService" class="org.example.service.impl.UserServiceImpl"/>
    
  • 配置切点表达式(那些方法被增强)

  • 配置织入(切点被那些通知方法增强,是前置增强还是后置增强)

     <!--aop配置-->
        <aop:config>
            <!--配置切点表达式,目的是要指定那些方法被增强-->
            <aop:pointcut id="myPointcut" expression="execution(void org.example.service.impl.UserServiceImpl.show1())"/>
            <!--配置织入,目的是要执行那些切点与那些通知结合-->
            <aop:aspect ref="advice"> 
                <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
                <aop:before method="beforeAdvice" pointcut="execution(void org.example.service.impl.UserServiceImpl.show2())"/>
            </aop:aspect>
        </aop:config>
    

切点表达式

  • 切点表达式要对那些连接点进行通知的增强

    execution([访问修饰符] 返回值 包名.类名.方法名(参数))
    
  • 访问修饰符可以省略

  • 返回值类型、某一级包名、类名、方法名可以使用 * 表示任意

  • 包名和类名之间使用单点 . 表示该包下的类,使用双 … 表示该包及其子包下的类

  • 参数列表可以使用两个点 … 表示任意参数

    在这里插入图片描述

AspectJ的通知类型

在这里插入图片描述

参数传递

在这里插入图片描述

  • JoinPoint对象

    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("当前目标对象"+joinPoint.getTarget());
        System.out.println("表达式:"+joinPoint.getStaticPart());
        System.out.println("前置增强");
    }
    
  • ProceedingJoinPoint对象

    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前的增强");
        //环绕目标方法
        Object res = proceedingJoinPoint.proceed();//执行目标方法
        System.out.println("环绕后的增强");
        return res;
    }
    
  • Throwable对象

    需要在配置文件中指定一个throwing—通知的参数名

    <!--异常抛出通知-->
    <aop:after-throwing throwing="e" method="afterThrowingAdvice" pointcut-ref="myPointcut"/>
    
    public void afterThrowingAdvice(Throwable e){
        System.out.println("当前异常信息是:"+e.getMessage());
        System.out.println("异常抛出通知...异常抛出才执行");
    }
    

语法形式

  • 使用< advisor >配置切面—声明式事务控制

    实现不同的接口来指定通知类型—环绕、前置、后置

    public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice {
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println("前置通知.....");
        }
    
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("后置通知....");
        }
    }
    

    xml配置

    <!--配置通知类-->
    <bean id="advice3" class="org.example.advice.MyAdvice3"/>
    <!--配置目标类-->
    <bean id="userService" class="org.example.service.impl.UserServiceImpl"/>
    <!--aop配置-->
    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* org.example.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="advice3" pointcut-ref="myPointcut"/>
    </aop:config>
    
  • 使用< aspect >配置切面

  • 不同点

    • 语法形式不同
      • advisor是通过实现接口来确认通知的类型
      • aspect是通过配置确认通知的类型,更加灵活
    • 可配置的切面数量不同
      • 一个advisor只能配置一个固定通知和一个切点表达式
      • 一个aspect可以配置多个通知和多个切点表达式任意组合
    • 使用场景不同
      • 允许随意搭配情况下可以使用aspect进行配置
      • 如果通知类型单一、切面单一的情况可以使用advisor进行配置
      • 在通知类型已经固定不用人为指定通知类型时,可以使用advisor进行配置,例如事务控制

原理刨析

  • 生成代理对象的两种方式

    在这里插入图片描述

  • 区别

    • CGlib基于父类生成Proxy

    在这里插入图片描述

注解方式配置AOP

基本使用

  • 增强类配置

    @Component
    @Aspect
    //增强类,内部提供增强方法
    public class MyAdvice {
    //    <aop:before method="beforeAdvice" pointcut="execution(* org.example.service.impl.*.*(..))"/>
        @Before("execution(* org.example.service.impl.*.*(..))")
        public void beforeAdvice(JoinPoint joinPoint){
            System.out.println("当前目标对象"+joinPoint.getTarget());
            System.out.println("表达式:"+joinPoint.getStaticPart());
            System.out.println("前置增强");
        }
    }
    
  • 切点表达式的抽取

    @Component
    @Aspect
    //增强类,内部提供增强方法
    public class MyAdvice {
        //切点表达式的抽取
        @Pointcut("execution(* org.example.service.impl.*.*(..))")
        @Around("MyAdvice.myPointcut()")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("环绕前的增强");
            //环绕目标方法
            Object res = proceedingJoinPoint.proceed();//执行目标方法
            System.out.println("环绕后的增强");
            return res;
        }
    }
    
  • 开启AOP自动代理

    配置类方式

    @Configuration
    @ComponentScan("org.example")
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class SpringConfig {
    }
    

    xml方式

    <aop:aspectj-autoproxy/>
    

    在这里插入图片描述

  • 注意

    pointcutvalue 效果是一样的

    @AfterThrowing(pointcut = "execution(* org.example.service.impl.*.*(..))",throwing = "e")
    

原理刨析

在这里插入图片描述

基于AOP的声明式事务控制

Spring事务概述

  • 不同数据库技术,事务控制不同,Spring统一了控制事务的接口

  • Spring事务分类

    在这里插入图片描述

  • Spring事务编程相关类

    在这里插入图片描述

xml方式的事务控制

基本使用

  • xml配置

    <!--配置平台事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置Spring提供好的Advice 需要一个平台事务管理器-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <-- 
               配置不同的方法的事务属性
               name:方法名称 需要被增强的方法的名称  * 代表通配符
               isolation:事务的隔离级别
            -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <!--配置切点表达式-->
        <aop:pointcut id="txPointcut" expression="execution(* org.example.service.impl.*.*(..))"/>
        <!--配置织入关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
    

详细配置

tx:attributes的详细配置
  • name — 方法名称

    • 需要被增强的方法的名称,可以使用通配符 addUser、addRole…->add*
  • isolation — 隔离级别

    指定事物的隔离级别、事务并发存在三大问题:脏读、不可重复读、幻读/虚读,常用的是READ_COMMITTEDREPEATABLE_READ

    在这里插入图片描述

  • timeout — 超时时间

    • 默认:-1 单位:秒
  • read-only — 是否只读

    • 查询操作才设置为只读
  • propagation — 事务的传播行为

    • 解决事务方法调用事务方法(事务嵌套问腿

    在这里插入图片描述

tx:method

一般会这样配置

<tx:attributes>
    <tx:method name="add*"/>
    <tx:method name="update*"/>
    <tx:method name="select*"/>
    <tx:method name="delete*"/>
    <tx:method name="*"/>
</tx:attributes>

原理

  • 本质是个Aop
  • 通过实现MethodInterceptor接口(advisor的实现),内部存在一个环绕通知,进行开事务、关事务、提交等

基于注解的事务控制

  • 需要注册事务平台管理器

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager =new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
    
  • @Transactional

    @Service
    //此Service中全部开启事务
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public class AccountServiceImpl implements AccountService {
        @Autowired
        private AccountMapper accountMapper;
        //单独事务
        @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
        @Override
        public void transferMoney(String outAccount, String inAccount, Integer money) {
            accountMapper.decrMoney(outAccount,money);
            int i=1/0;
            accountMapper.incrMoney(inAccount,money);
        }
        public void registerAccount(){
    
        }
    }
    
  • 开启事务自动代理

    注解方式,会自动找DataSourceTransactionManager这个类型

    @EnableTransactionManagement
    

    xml方式

    如果配置的平台事务管理器不是transactionManager(默认值),需要加transaction-manager指定平台事务管理器

    <!--配置平台事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager"/>
    

切点表达式配置方法与切点表达式事务属性配置的区别

  • <aop:pointcut id=“txPointcut” expression="execution( org.example.service.impl..(…))*"/>与<tx:method name=“add*”/>的区别
<!--配置Spring提供好的Advice-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-method name="add*" 配置不同方法的事务属性 ->
        <tx:method name="add*"/>
        <tx:method name="update*"/>
        <tx:method name="select*"/>
        <tx:method name="delete*"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
<aop:config>
    <!--配置切点表达式-->
    <!- expression="execution(* org.example.service.impl.*.*(..))"筛选那些切点符合要求,需要被增强-->
    <aop:pointcut id="txPointcut" expression="execution(* org.example.service.impl.*.*(..))"/>
    <!--配置织入关系-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

在这里插入图片描述

Spring整合Web环境

JavaWeb三大组件及环境特点

在这里插入图片描述

Spring整合Web环境的思路和实现

整合Web环境需要解决的问题

  • ApplicationContext创建一次,配置加载一次
  • 最好Web服务器启动时,就创建Spring容器
  • ApplicationContext的引用需要在web层任何位置都可以访问到

思路

  • 在ServletContextListener的contextInitialized(监听ServletContext域创建的方法)方法中执行ApplicationContext的创建。或者在Servlet的init方法中执行ApplicationContext的创建,并给Servlet的load-on-startup属性一个数字值,确保服务器启动Servlet就创建
  • 将创建好的ApplicationContext存储到ServletContext域中,使整个Web层都可以获取

整合Web环境的实现

  • 通过Listener在服务器启动时创建Spring容器,并将Spring容器存储到ServletContext域中

    public class ContextLoaderListener implements ServletContextListener {
        private final String CONTEXT_CONFIG_LOCATION="contextConfigLocation";
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            System.out.println("ContextLoaderListener init.....");
            //获得ServletContext
            ServletContext servletContext = sce.getServletContext();
            //0.获取ContextConfigLocation配置文件的名称
            String contextConfigLocation = servletContext.getInitParameter(CONTEXT_CONFIG_LOCATION);
            //解析出配置文件的名称
            contextConfigLocation = contextConfigLocation.substring("classpath:".length());
            //1.创建Spring容器
            ClassPathXmlApplicationContext app =new ClassPathXmlApplicationContext(contextConfigLocation);
            //2.将容器存储到ServletContext域中
           servletContext.setAttribute("applicationContext",app);
        }
    }
    
  • 注册监听器,并且将Spring配置文件的名称让使用者自己定义

    <!--定义全局参数-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:ApplicationContext.xml</param-value>
    </context-param>
    <!--配置Listener-->
    <listener>
        <listener-class>org.example.listener.ContextLoaderListener</listener-class>
    </listener>
    
  • 将从ServletContext中获取Spring的操作进行封装

    public class WebApplicationUtils {
        public static ApplicationContext getWebApplicationContext(ServletContext servletContext){
            return (ApplicationContext) servletContext.getAttribute("applicationContext");
        }
    }
    
  • 测试代码

    @WebServlet("/accountServlet")
    public class AccountServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            ServletContext servletContext = req.getServletContext();
            ApplicationContext app = WebApplicationUtils.getWebApplicationContext(servletContext);
            AccountService accountService = app.getBean(AccountService.class);
            accountService.transferMoney("tom","lucy",500);
        }
    }
    

Spring配置Spring-Web —使用xml方式

  • 坐标引入

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>
    
  • web.xml配置

<!--定义全局参数-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:ApplicationContext.xml</param-value>
</context-param>
<!--配置Listener-->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  • 测试

    @WebServlet("/accountServlet")
    public class AccountServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            ServletContext servletContext = req.getServletContext();
            ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
            AccountService accountService = app.getBean(AccountService.class);
            accountService.transferMoney("tom","lucy",500);
        }
    }
    

Spring配置Spring-Web —使用注解方式

  • web.xml配置

    <!--定义参数-->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.example.config.MyAnnotationConfigWebApplicationContext</param-value>
    </context-param>
    
  • 配置类配置

    public class MyAnnotationConfigWebApplicationContext extends AnnotationConfigWebApplicationContext {
        public MyAnnotationConfigWebApplicationContext(){
            super();
            this.register(SpringConfig.class);
        }
    }
    

SpringMVC框架思想

  • 原始JavaWeb开发的问题,Servlet充当Controller,Jsp充当View角色,JavaBean充当模型对象

在这里插入图片描述

  • 设计思路

    在这里插入图片描述

    • 负责共有行为的Servlet称为前端控制器,负责业务行为的JavaBean称之为控制器Controller

    • 前端控制器基本功能

      • 具备可以映射到业务Bean的能力

        将前端控制器的共有行为进行分发,与业务行为共同组成一个Servlet

      • 具备可以解析请求参数,封装实体等共有功能

      • 具备响应视图及响应其他数据的功能,json转换为JavaBean

SpringMVC简介

概述

  • SpringMVC是一个基于Spring开发的MVC轻量级框架,Spring3.0发布的组件,SpringMVC和Spring可以无缝整合,使用DisPatcherServlet作为前端控制器,内部提供处理器映射器、处理器适配器、视图解析器等组件,可以简化JavaBean的封装,Json转化、文件上传操作等。

    在这里插入图片描述

入门

  • 流程

    在这里插入图片描述

  • web.xml配置

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <!--配置DispatcherServlet-->
        <servlet>
            <servlet-name>DispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--指定配置文件-->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:ApplicationContext.xml</param-value>
            </init-param>
            <load-on-startup>2</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>DispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    
  • SpringMVC配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            ">
        <!--组件扫描-->
        <context:component-scan base-package="org.example"/>
    </beans>
    
  • java测试

    @Controller
    public class QuickController {
        @RequestMapping("/show")
        public void show(){
            System.out.println("show running");
        }
    }
    

Controller中访问Spring容器中的Bean

  • 配置Spring的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            ">
        <!--组件扫描-->
        <context:component-scan base-package="org.example"/>
    </beans>
    
  • 在web.xml中注册

    <!--配置ContextLoader-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:ApplicationContext.xml</param-value>
    </context-param>
    
  • 注入

    @Resource
    private UserService userService;
    

SpringMVC关键组件的解析

在这里插入图片描述

  • 三个组件的关系

    在这里插入图片描述

  • 如果不在Spring-MVC中配置组件,前端控制器会默认加载三个组件

    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
        org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
        org.springframework.web.servlet.function.support.RouterFunctionMapping
    

SpringMVC的请求处理

请求映射路径的配置

在这里插入图片描述

请求数据接收

普通数据的接收

  • @RequestParam的属性:
    • value:请求传过来值对应的参数的值
    • required:该参数是否是必须的,默认值为true
    • defaultValue:该参数必须时不提交的默认值
  • 请求参数和形参名称相同时,SpringMVC会直接对应赋值,不同时需要使用**@RequestParam指定请求参数的值**
  • 请求参数有多个相同值时,可以使用数组和集合进行接收,但是需要使用**@RequestParam标识**
//    http://localhost:8080/SpringMVCDemo02/parma2?username=za&age=10
    @GetMapping(("/parma1"))
    public String param1(String username, int age){
        System.out.println(username+"===="+age);
        return "/index.jsp";
    }
//    http://localhost:8080/SpringMVCDemo02/parma2?username=za&age=10
    @GetMapping(("/parma2"))
    public String param2(@RequestParam("username") String name, int age){
        System.out.println(name+"===="+age);
        return "/index.jsp";
    }
//    http://localhost:8080/SpringMVCDemo02/parma3?hobby=za&hobby=io
    @GetMapping(("/parma3"))
    public String param3(String[] hobby){
        System.out.println(Arrays.toString(Arrays.stream(hobby).toArray()));
        return "/index.jsp";
    }
//    http://localhost:8080/SpringMVCDemo02/parma4?hobby=za&hobby=io
    @GetMapping(("/parma4"))
    public String param4(@RequestParam List<String> hobby){
        hobby.forEach(System.out::println);
        return "/index.jsp";
    }
//    http://localhost:8080/SpringMVCDemo02/parma5?username=za&age=10
    @GetMapping(("/parma5"))
    public String param5(@RequestParam Map<String,String> map){
        map.forEach((k,v)->{
            System.out.println(k+"=="+v);
        });
        return "/index.jsp";
    }

对象类型的参数 —SpringMVC会自动创建对象并将属性设置

//    http://localhost:8080/SpringMVCDemo02/parma6?id=1&username=zhangsan&hobbies=ui&hobbies=op&birthday=2004/01/23&address.city=binghan&address.area=dfdf
    @GetMapping(("/parma6"))
    public String param6(User user){
        System.out.println(user.toString());
        return "/index.jsp";
    }

Json格式数据 —使用JSON字符串解析

  • 手动方式 —不推荐

        @GetMapping(("/parma7"))
        public String param7(@RequestBody String body){
            //将json的字符串转换为对象
            User user = JSON.parseObject(body,User.class);
            System.out.println(user.toString());
            return "/index.jsp";
        }
    
  • 配置转换器到RequestMappingHandlerAdapter中,实现自动转换

    <!--配置HandlerAdapter-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
            </list>
        </property>
    </bean>
    
  • 自动转换

        @GetMapping(("/parma7"))
        public String param7(@RequestBody User user){
            System.out.println(user.toString());
            return "/index.jsp";
        }
    

Restful风格数据

  • 用URI表示某个模块资源,资源名称为名词

    在这里插入图片描述

  • 用请求方式表示模块具体业务动作

    在这里插入图片描述

  • 用HTTP响应码表示结果,国内常用响应包括三部分:状态码、状态信息、响应数据

  • 使用**@PathVariable**获得路径参数

        @GetMapping(("/parma8/{id}/{xxx}"))
        public String param8(
                @PathVariable("id") int id,
                @PathVariable("xxx") String yyy
        ){
            System.out.println(id+"---"+yyy);
            return "/index.jsp";
        }
    

文件上传

  • 表单的提交方式必须是POST

  • 表单的enctype属性必须事multipart/form-data

  • 文件上传需要有name属性

    <form>
        <input type="file" name="myFile">
    </form>
    
  • springMVC配置 — Spring6以上需要使用StandardServletMultipartResolver

    <!--配置文件上传解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
    
  • 需要在web.xml的DispatcherServlet中添加配置

    <!--配置DispatcherServlet-->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--指定配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
        <!-multipart配置 ->
        <multipart-config>
            <max-file-size>10485760</max-file-size>
            <max-request-size>10485760</max-request-size>
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>
    </servlet>
    
  • 文件上传的基本流程

    上传多个文件可以使用MultipartFile[ ]

    @PostMapping(("/parma9"))
    public String param9(@RequestBody MultipartFile myFile) throws IOException {
        //将上传的文件进行保存
        //1.获得当前上传文件的输入流
        InputStream inputStream = myFile.getInputStream();
        //2.获得文件上传位置的输出流
        OutputStream outputStream = Files.newOutputStream(Paths.get("D:\\Project\\SpringClassDemo\\" + myFile.getOriginalFilename()));
        //3.执行文件拷贝
        IOUtils.copy(inputStream,outputStream);
        //4.关闭流
        inputStream.close();
        outputStream.close();
        return "/index.jsp";
    }
    

请求头参数接收

  • 获取单个头信息

    使用**@RequestHeader(“Accept-Encoding”),加参数**

    @GetMapping(("/parma10"))
    public String param10(@RequestHeader("Accept-Encoding") String headerValue){
        System.out.println(headerValue);
        return "/index.jsp";
    }
    
  • 获得所有头信息

    不指定参数

    @GetMapping(("/parma11"))
    public String param11(@RequestHeader Map<String, String> map) {
        map.forEach((k, v) -> {
            System.out.println(k + "->" + v);
        });
        return "/index.jsp";
    }
    
  • 获得Cookie

    使用@CookieValue(value = “JSESSIONID”,defaultValue = “”),第一次访问可能没有cookie,默认值就为空

    • JSESSIONID的由来

    在这里插入图片描述

    @GetMapping(("/parma12"))
    public String param12(@CookieValue(value = "JSESSIONID",defaultValue = "") String cookie){
        System.out.println(cookie);
        return "/index.jsp";
    

Request域中数据的转发与获取

@RequestAttribute获取Request域中的值

@GetMapping(("/request1"))
public String request1(HttpServletRequest request){
   //向Request中存数据
    request.setAttribute("username","zhangsan");
    return "/request2";
}
@GetMapping(("/request2"))
public String request2(@RequestAttribute("username") String username){
    System.out.println(username);
    return "/index.jsp";
}

@SessionAttribute可以直接获取Session

获取JavaWeb常用对象

需要什么对象,直接在方法上定义,SpringMVC会直接注入

@RequestMapping(("/parma13"))
public String parma13(HttpServletRequest request, HttpServletResponse response){
    System.out.println(request);
    System.out.println(response);
    return "/index.jsp";
}

静态资源请求

  • 请求不到的原因:自己配置的DispatcherServlet原本tomcat的default替换掉了,spring的DispatcherServlet 不具备返回静态资源的功能,所以找不到静态资源

  • 方法一:再次激活原本的DefaultServlet

    <!--再次激活DefaultServlet url-pattern更加精确-->
    <servlet-mapping>
        <servlet-name>    
        <servlet-mapping>
            <servlet-name>default</servlet-name>
            <!--扩展名匹配-->
            <url-pattern>*.html</url-pattern>
        </servlet-mapping>
        <servlet-mapping>
            <servlet-name>default</servlet-name>
            <!--目录匹配-->
            <url-pattern>/img/*</url-pattern>
        </servlet-mapping></servlet-name>
        <!--扩展名匹配-->
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <!--目录匹配-->
        <url-pattern>/img/*</url-pattern>
    </servlet-mapping>
    
  • 方法二:在spring-mvc.xml中去配置静态资源、匹配映射路径的请求到指定的位置去匹配资源

    <!--配置静态资源的映射路径-->
    <mvc:resources mapping="/img/*" location="/img/"/>
    
  • 方法三:在Spring-mvc.xml中配置< mvc:default-servlet-handler />,该配置会注册一个DefaultServletRequestHandler处理器,静态资源的访问都由该处理器去处理

    <!-静态资源的访问->
    <mvc:default-servlet-handler/>
    

注解驱动< mvc:annotation-driven>标签

  • 不配置RequestMappingHandlerMapping,只配置静态资源访问,访问不到路径的原因

    • < mvc:default-servlet-handler/>这个标签会自动注册一个HandlerMapping不配置RequestMappingHandlerMapping就不会加载spring加载策略中默认的三个HandlerMapping
        <!--配置HandlerMapping-->
        <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
    	<!--配置静态资源访问-->
        <mvc:default-servlet-handler/>
    
  • 使用 MVC注解驱动 — < mvc:annotation-driven/>

    <!--mvc注解驱动-->
    <mvc:annotation-driven/>
    

    相当于

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
    <!--配置HandlerAdapter-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
            </list>
        </property>
    </bean>
        <!--配置静态资源的映射路径-->
    <!--    <mvc:resources mapping="/img/*" location="/img/"/>-->
    <mvc:default-servlet-handler/>
    

SpringMVC的响应处理

传统同步业务数据响应

  • 请求资源转发

    在这里插入图片描述

  • 请求资源重定向

    在这里插入图片描述

  • 响应模型数据

    使用ModelAndView将模型转发给页面,页面使用el表达式接收

    //转发携带数据
    @GetMapping("/res3")
    public ModelAndView res3(ModelAndView modelAndView){
        //ModelAndView 封装模型数据和视图名
        //设置模型数据
        User user=new User();
        user.setUsername("zhangsan");
        user.setId(1);
        modelAndView.addObject("user",user);
        //设置视图名称给,在页面中展示数据
        modelAndView.setViewName("/index.jsp");
        return modelAndView;
    }
    
  • 直接回写数据给客户端

    使用注解**@ResponseBody**

    //直接回写字符串
    @GetMapping("/res4")
    //返回的是响应体,不是视图
    @ResponseBody
    public String res4(){
        return "hello SpringMVC";
    }
    

前后端分离异步业务数据响应

  • 同步方式回写数据,是将数据响应浏览器进行页面展示,异步方式回写数据一般是写给Ajax引擎,即谁访问服务端,服务端就将数据响应给谁

  • 同步方式回写数据,一般就是一些无特定格式的字符串,异步方式回写数据大多是JSON格式字符串

    @ResponseBody

    • 会自动将对象转换为JSON字符串,并且设置响应头为application/json

    @RestController

    • 集成了@Controller和@ResponseBody

SpringMVC拦截器

简介

  • SpringMVc拦截器规范,主要是对Controller资源访问时进行拦截操作的技术,当然拦截之后可以进行权限控制,功能增强等都是可以的,类似于JavaWeb开发中的Filter

    在这里插入图片描述

  • Filter和Interceptor区别

    在这里插入图片描述

  • HandlerInterceptor 接口方法的作用及其参数

    在这里插入图片描述

Interceptor使用

  • 实现HandlerInterceptor

    public class MyInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("MyInterceptor.....preHandle");
            return false;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("MyInterceptor.....postHandle");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("MyInterceptor......afterCompletion");
        }
    }
    
  • 在SpringMVC中配置Interceptor

    <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--对那些请求进行拦截-->
            <mvc:mapping path="/**"/>
            <bean class="org.example.interceptors.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    

多个拦截器执行顺序

  • 执行顺序 — 先配谁谁先执行

    <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--对那些请求进行拦截-->
            <mvc:mapping path="/**"/>
            <bean class="org.example.interceptors.MyInterceptor1"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <!--对那些请求进行拦截-->
            <mvc:mapping path="/**"/>
            <bean class="org.example.interceptors.MyInterceptor2"/>
        </mvc:interceptor>
    </mvc:interceptors>
    
  • 内部方法执行顺序

    只要一个拦截器不放行,就不会到Handler(处理器)

    • 当interceptor全都放行时

      在这里插入图片描述

    • 当interceptor1和interceptor2放行,interceptor3不放行时

      在这里插入图片描述

原理

在这里插入图片描述

SpringMVC的全注解开发

spring-mvc中组件转换为注解方式

  • 和spring加载组件是一样的

    @Configuration
    @ComponentScan("org.example")
    @EnableWebMvc
    public class SpringMvcConfig {
        @Bean
        public StandardServletMultipartResolver standardServletMultipartResolver(){
            return new StandardServletMultipartResolver();
        }
    }
    
  • @EnableWebMvc相当于**< mvc:annotation-driven>**,加载一些基本的组件

  • 实现WebMvcConfigurer接口后springMVC会自动注入实现了该接口的类

  • 配置拦截器

    实现WebMvcConfigurer接口后,使用addInterceptors方法,进行添加

    @Component
    public class MyWebMvcConfigurer implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //添加一个拦截器并且去配置他的拦截路径
            registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
            registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");
        }
    
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            //开启默认的Servlet处理器,来处理静态资源
            //重写就会自动开启
            //相当于     <!--    <mvc:resources mapping="/img/*" location="/img/"/>-->
    		//			<!--    <mvc:default-servlet-handler/>-->
    
        }
    }
    

DispatchServlet加载核心配置类

  • 继承AnnotationConfigWebApplicationContext类后,调用构造方法,将SpringMvc的配置类进行注册

    public class MyAnnotationConfigWebApplicationContext extends AnnotationConfigWebApplicationContext {
        public MyAnnotationConfigWebApplicationContext(){
            //注册
            super.register(SpringMvcConfig.class);
        }
    }
    
  • 在web.xml中加载核心配置类

    <!--配置DispatcherServlet-->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--加载SpringMVC的配置文件   XML方式-->
      <!--  <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>-->
        <!--加载SpringMVC的核心配置类  注解方式-->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.example.config.MyAnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
        <multipart-config>
            <max-file-size>10485760</max-file-size>
            <max-request-size>10485760</max-request-size>
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>
    </servlet>
    

消除Web.xml

  • Servlet3.0环境中,web容器提供了jakarta.servlet.ServletContainerInitializer接口,实现了该接口后,在对应的类加载路径META-INF/services 目录创建一个名为jakarta.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那,当Web容器启动时就会运行这个初始化容器做一些组件内的初始化工作,从而可以实现替换web.xml

    • 实现了ServletContainerInitializer的类

      @HandlesTypes({})会将()中的值的子类或者实现类加载到 onStartup方法的 set中

      @HandlesTypes({})
      public class MyServletContainerInitializer implements ServletContainerInitializer {
          @Override
          public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
              System.out.println("MyServletContainerInitializer running.....");
          }
      }
      
    • jakarta.servlet.ServletContainerInitializer文件

      在这里插入图片描述

  • 基于上面的特性,Spring就定义了一个SpringServletContainerInitializer实现ServletContainerInitializer接口(可以使用Spring提供的方法,来进行Spring和SpringMVC的配置)

  • 而SpringServletContainerInitializer就会查找实现了WebApplicationInitializer的类,Spring又提供了一个WebApplicationInitializer的基础实现类AbstractAnnotationConfigDispatcherServletInitializer,当我们编写类继承AbstractAnnotationConfigDispatcherServletInitializer时,容器就会自动发现我们自己的类,在该类中我们就可以配置SpringSpringMVC的入口了,从而实现消除web.xml,总的来说,服务器在加载web的时候会去加载web.xml配置,如果找不到就会找实现了ServletContainerInitializer的类,通过他去加载配置,组件

    public class MyAbstractAnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        //提供Spring容器的配置类
        protected Class<?>[] getRootConfigClasses() {
            return new Class[]{SpringConfig.class};
        }
        //提供SpringMVC容器的配置类
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
        //提供前端控制器的映射路径
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    }
    

SpringMVC的组件原理刨析

前端控制器初始化

前端控制器DispatcherServlet时SpringMVC的入口,也是SpringMVC的大脑,主流程工作的都是在此完成。DispatcherServlet本质就是一个Servlet,当配置了 load-on-startup时,会在服务器启动时就执行创建和初始化init方法,每次请求都会执行service方法

DispatchServlet的初始化

  • 获得一个SpringMVC的ApplicationContext的容器
  • 注册了SpringMVC的九大组件

SpringMVC的异常处理机制

SpringMVC异常的编译流程

  • SpringMVC处理异常的思路

    在这里插入图片描述

SpringMVC的异常处理方式

  • 简单异常处理器:使用SpringMVC内置的异常处理器处理SimpleMappingExceptionResolver

    配置SimpleMappingExceptionResolver到SpringMVC

    //配置简单的异常处理类
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver simpleMappingExceptionResolver =new SimpleMappingExceptionResolver();
        //不管什么异常,统一响应友好页面
        simpleMappingExceptionResolver.setDefaultErrorView("/error1.html");
        //区分异常类型,根据不同的异常可以跳转不同的视图
        Properties properties = new Properties();//键值对:key:异常对象全限定名,value:跳转的视图
        properties.setProperty("java.lang.RuntimeException","/error1.html");
        properties.setProperty("java.io.FileNotFoundException","/error2.html");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        return simpleMappingExceptionResolver;
    }
    
  • 自定义异常处理器:实现HandlerExceptionResolver接口,自定义异常进行处理

    自定义类实现

    @Component
    public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
        @Override
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
            //简单响应一个友好提示页面
            /*ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("/error1.html");*/
            //响应JSON格式数据
            String resultJson = "{\"code:\":0,\"message\":'成功',\"data\":11}";
            try {
                response.getWriter().print(resultJson);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return null;
        }
    }
    
  • 注解方式:使用@ControllerAdvice+@ExceptionHandler处理

    @ControllerAdvice标注该类是一个异常处理类

    @ExceptionHandler该方法捕获的是什么异常

    @ControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler(RuntimeException.class)
        public ModelAndView runtimeException(RuntimeException e){
            System.out.println(e.getMessage());
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("/error1.html");
            return modelAndView;
        }
        @ExceptionHandler(IOException.class)
        @ResponseBody
        public Result<Object> fileNotFoundException(IOException e){
            System.out.println(e.getMessage());
            Result<Object> result = new Result<>(100,"xxx",new Object());
            return result;
        }
    }
    

SpringMVC常用的异常解析器

在这里插入图片描述

C的入口,也是SpringMVC的大脑,主流程工作的都是在此完成。DispatcherServlet本质就是一个Servlet,当配置了 load-on-startup时,会在服务器启动时就执行创建和初始化init方法,每次请求都会执行service方法

DispatchServlet的初始化

  • 获得一个SpringMVC的ApplicationContext的容器
  • 注册了SpringMVC的九大组件

SpringMVC的异常处理机制

SpringMVC异常的编译流程

  • SpringMVC处理异常的思路

    [外链图片转存中…(img-IpSQRh3d-1736999315567)]

SpringMVC的异常处理方式

  • 简单异常处理器:使用SpringMVC内置的异常处理器处理SimpleMappingExceptionResolver

    配置SimpleMappingExceptionResolver到SpringMVC

    //配置简单的异常处理类
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver simpleMappingExceptionResolver =new SimpleMappingExceptionResolver();
        //不管什么异常,统一响应友好页面
        simpleMappingExceptionResolver.setDefaultErrorView("/error1.html");
        //区分异常类型,根据不同的异常可以跳转不同的视图
        Properties properties = new Properties();//键值对:key:异常对象全限定名,value:跳转的视图
        properties.setProperty("java.lang.RuntimeException","/error1.html");
        properties.setProperty("java.io.FileNotFoundException","/error2.html");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        return simpleMappingExceptionResolver;
    }
    
  • 自定义异常处理器:实现HandlerExceptionResolver接口,自定义异常进行处理

    自定义类实现

    @Component
    public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
        @Override
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
            //简单响应一个友好提示页面
            /*ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("/error1.html");*/
            //响应JSON格式数据
            String resultJson = "{\"code:\":0,\"message\":'成功',\"data\":11}";
            try {
                response.getWriter().print(resultJson);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return null;
        }
    }
    
  • 注解方式:使用@ControllerAdvice+@ExceptionHandler处理

    @ControllerAdvice标注该类是一个异常处理类

    @ExceptionHandler该方法捕获的是什么异常

    @ControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler(RuntimeException.class)
        public ModelAndView runtimeException(RuntimeException e){
            System.out.println(e.getMessage());
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("/error1.html");
            return modelAndView;
        }
        @ExceptionHandler(IOException.class)
        @ResponseBody
        public Result<Object> fileNotFoundException(IOException e){
            System.out.println(e.getMessage());
            Result<Object> result = new Result<>(100,"xxx",new Object());
            return result;
        }
    }
    

SpringMVC常用的异常解析器

[外链图片转存中…(img-6ezQ2qZR-1736999315567)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值