Spring6学习

Spring 6

环境要求

  1. JDK 17+ (Spring6 要求 JDK 最低版本是 JDK17)
  2. Maven 3.6+
  3. Spring 6.0.x

Log4j2

Log4j2 概念

Apache Log4j2 是一个开源日志记录组件,使用非常广泛。在工程中方便替换 System.out 等打印语句,它是 java 下最流行的的日志输入工具。
Log4j2 主要由几个重要的组件构成:

  1. 日志信息的优先级
    日志信息的优先级顺序为 TRACE < DEBUG < INFO < WARN < ERROR < FATAL
    • TRACE 追踪,是最低的日志级别,相当于追踪程序的执行
    • DEBUG 调试,一般在开发中,都将其设置为最低的日志级别
    • INFO 信息,输出重要信息,使用较多
    • WARN 警告,输出警告信息
    • ERROR 错误,输出错误信息
    • FATAL 严重错误
      这些日志分别用来指定这条日志信息的重要程度,级别高的会自动屏蔽级别低的日志,也就是设置了 WARN 的日志,则 INFO、DEBUG 日志级别的日志不会显示出来。
  2. 日志信息的输出目的地
    日志输入的目的地指定了将日志输出到控制台还是文件中。
  3. 日志信息的输出格式
    日志输出格式则控制日志信息的显示内容。

Log4j2 依赖

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.20.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j2-impl</artifactId>
    <version>2.20.0</version>
</dependency>

添加 log4j2 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <!--<Configuration status="WARN" monitorInterval="30"> -->
    <properties>
        <property name="LOG_HOME">./service-logs</property>
    </properties>
    <Appenders>
        <!--*********************控制台日志***********************-->
        <Console name="consoleAppender" target="SYSTEM_OUT">
            <!--设置日志格式及颜色-->
            <PatternLayout
                    pattern="%style{%d{ISO8601}}{bright,green} %highlight{%-5level} [%style{%t}{bright,blue}] %style{%C{}}{bright,yellow}: %msg%n%style{%throwable}{red}"
                    disableAnsi="false" noConsoleNoAnsi="false"/>
        </Console>

        <!--*********************文件日志***********************-->
        <!--all级别日志-->
        <RollingFile name="allFileAppender"
                     fileName="${LOG_HOME}/all.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/all-%d{yyyy-MM-dd}-%i.log.gz">
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>

        <!--debug级别日志-->
        <RollingFile name="debugFileAppender"
                     fileName="${LOG_HOME}/debug.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--过滤掉info及更高级别日志-->
                <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>

        <!--info级别日志-->
        <RollingFile name="infoFileAppender"
                     fileName="${LOG_HOME}/info.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--过滤掉warn及更高级别日志-->
                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <!--<DefaultRolloverStrategy max="100"/>-->
        </RollingFile>

        <!--warn级别日志-->
        <RollingFile name="warnFileAppender"
                     fileName="${LOG_HOME}/warn.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--过滤掉error及更高级别日志-->
                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>

        <!--error及更高级别日志-->
        <RollingFile name="errorFileAppender"
                     fileName="${LOG_HOME}/error.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>

        <!--json格式error级别日志-->
<!--        <RollingFile name="errorJsonAppender"-->
<!--                     fileName="${LOG_HOME}/error-json.log"-->
<!--                     filePattern="${LOG_HOME}/error-json-%d{yyyy-MM-dd}-%i.log.gz">-->
<!--            <JSONLayout compact="true" eventEol="true" locationInfo="true"/>-->
<!--            <Policies>-->
<!--                <SizeBasedTriggeringPolicy size="100 MB"/>-->
<!--                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>-->
<!--            </Policies>-->
<!--        </RollingFile>-->
    </Appenders>

    <Loggers>
        <!-- 根日志设置 -->
        <Root level="debug">
            <AppenderRef ref="allFileAppender" level="all"/>
            <AppenderRef ref="consoleAppender" level="debug"/>
            <AppenderRef ref="debugFileAppender" level="debug"/>
            <AppenderRef ref="infoFileAppender" level="info"/>
            <AppenderRef ref="warnFileAppender" level="warn"/>
            <AppenderRef ref="errorFileAppender" level="error"/>
       <!--            <AppenderRef ref="errorJsonAppender" level="error"/>-->
        </Root>

        <!--spring日志-->
        <Logger name="org.springframework" level="debug"/>
        <!--druid数据源日志-->
        <Logger name="druid.sql.Statement" level="warn"/>
        <!-- mybatis日志 -->
        <Logger name="com.mybatis" level="warn"/>
        <Logger name="org.hibernate" level="warn"/>
    </Loggers>

</Configuration>

创建 logger 对象并使用

private Logger logger = LoggerFactory.getLogger(UserTest.class);
logger.debug("1111111");

IOC

概念

  • IOC (控制反转),控制反转是一种思想
  • 控制反转是为了降低程序的耦合度,提高程序的扩展力
  • 控制反转,反转的是什么?
    • 将对象的创建权利交出去,交给第三方的容器去负责
    • 将对象和对象之间的关系的维护交出去,交给第三方容器去负责
  • 控制反转是如何实现的?
    DI (Dependency injection) 依赖注入

依赖注入
指 Spring 创建对象过程中,将对象依赖的属性通过配置进行注入
依赖注入常见的实现方式有两种:

  1. set 注入
  2. 构造器注入

IOC 是一种控制反转的思想,而 DI 是对 IOC 思想的一种具体实现

Bean 管理意思是:Bean 对象的创建,以及 Bean 对象中属性的赋值(或者叫 Bean 对象之间的关系维护)

Spring 通过 IOC 容器来管理 所有 Java 对象的实例化和初始化,控制对象之间的依赖关系
IOC 容器管理的 java 对象称为 Spring Bean

IOC容器在Spring中的实现

Spring 中的 IOC 容器是 IOC 思想落地产品实现。IOC 容器中管理组件也叫做 bean,在 bean 创建之前,首先要创建 IOC 容器。Spring 提供了两种 IOC 容器的实现方式。

  1. BeanFactory
    这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用
  2. ApplicationContext
    BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是 BeanFacatory

ApplicationContext

ApplicationContext 的实现类:

  • ClassPathXmlApplicationContext
    通过读取类路径下的 xml 格式的配置文件创建 IOC 容器对象
  • FileSystemXmlApplicationContext
    通过文件系统路径读取 xml 格式的配置文件创建 IOC 容器对象
  • ConfigurableApplicationContext
    ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close(),让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
  • WebApplicationContext
    专门为 Web 应用准备,基于 Web 环境创建 IOC 容器,并将对象引用存入 ServletContext 域中。

基于XML的IOC配置

<?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="user" class="cn.xx.beans.User"></bean>
</beans>

bean 标签中 id 和 name 的区别

在 Spring 中,bean 的 id 和 name 都是用于唯一标识一个 bean 的属性。

  1. id 属性只能定义一个唯一标识符,而 name 属性可以定义多个 逗号或分号分隔 的标识符。如果没有指定 id 属性,那么 name 第一个值就是 id。
  2. id 属性值只能是字符串,而 name 属性可以使用任何有效的 Java 标识符。这意味着 name 属性可以包含特殊字符(例如“-”或“.”)。
  3. 如果同时定义了 id 和 name 属性,则 id 属性的值将作为 bean 的默认名称。
    总的来说,id 属性用于唯一标识 bean,而 name 属性是可选的且用于定义多个别名。
  4. 如果 一个 bean 标签未指定 id、name 属性,则 spring 容器会给其一个默认的 id,值为其类全名。
    如果有多个 bean 标签未指定 id、name 属性,则 spring 容器会按照其出现的次序,分别给其指定 id 值为 “类全名#1”, “类全名#2”

Bean 的获取方式

  1. 根据 id 获取 bean
 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
 Object user = context.getBean("user");
  1. 根据 class 类型获取 bean
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean(User.class);
  1. 根据 id 和 class 类型获取 bean
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);

注意:当根据 class 类型获取 bean 时,要求 IOC 容器中指定类型的 bean 有且只有一个。否则会抛出 NoUniqueBeanDefinitionException 错误

  1. 如果组件类实现了接口,根据接口类型可以获取 bean 吗?
    如果 bean 唯一,是可以的。

  2. 如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取bean 吗?
    不可以,因为此时 bean 不唯一。

根据类型来获取 bean 时,在满足 bean 是唯一性的前提下,其实只是看 对象 instanceof 指定类型 的返回结果,只要返回 true,就认为和类型匹配,就能获取到。

set 方法注入属性

<?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">
   
    <!--set 方法注入-->
    <bean id="userSetter" class="cn.xx.beans.User">
        <property name="name" value="admin"></property>
        <property name="age" value="20"></property>
    </bean>
</beans>

构造器注入属性

<?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="userConstructor" class="cn.xx.beans.User">
        <constructor-arg name="name" value="admin"></constructor-arg>
        <constructor-arg name="age" value="28"></constructor-arg>
    </bean>

</beans>
特殊值处理
  1. null 值
    以下举例表示 address 属性赋值为 null
<?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="userSetter" class="cn.xx.beans.User">
        <property name="name" value="admin"></property>
        <property name="age" value="20"></property>
        <property name="address">
        	<null></null>
        </property>
    </bean>
</beans>
  1. 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="userSetter" class="cn.xx.beans.User">
        <property name="name" value="$lt; $gt;"></property>
        <property name="age" value="20"></property>
        
    </bean>
</beans>
  1. CDATA 节
<!-- CDATA 表示纯文本数据 -->
 <property name="expression" value="![CDATA[a < b]]"></property>

引入外部 bean 注入对象属性

通过 property 标签的 ref 属性来引入外部 bean

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

    <!--
    引入外部 bean 的方式赋值
    1. 创建两个类的对象 emp 和 dep
    2. 在 emp 的 bean 标签中,使用 property 引入 dep 的 bean
    -->
    <bean name="dep" class="cn.xx.beans.Dep">
        <property name="depname" value="scurity"></property>
    </bean>

    <bean name="emp1" class="cn.xx.beans.Emp">
        <property name="ename" value="admin"></property>
        <property name="age" value="20"></property>
        <property name="dep" ref="dep"></property>
    </bean>
</beans>

使用内部 bean 方式注入对象属性

通过 property 标签中创建一个 bean 标签来注入对象。

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

    <!--
   使用内部 bean 的方式赋值
   1. 创建对象 emp
   2. 在 emp 的 bean 标签中的 property 标签中,再增加一个 bean,在此 bean 标签中注册 dep 对象
   -->

    <bean name="emp2" class="cn.xx.beans.Emp">
        <property name="ename" value="admin"></property>
        <property name="age" value="20"></property>
        <property name="dep">
            <bean name="dep2" class="cn.xx.beans.Dep">
                <property name="depname" value="scurity2"></property>
            </bean>
        </property>
    </bean>
</beans>

级联属性赋值

<?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 name="dep" class="cn.xx.beans.Dep">
        <property name="depname" value="scurity"></property>
    </bean>

    <bean name="emp3" class="cn.xx.beans.Emp">
        <property name="ename" value="admin"></property>
        <property name="age" value="20"></property>
        <property name="dep" ref="dep"></property>
        <!--级联属性赋值-->
        <property name="dep.depname" value="os-dep"></property>
    </bean>
</beans>

数组类型数据注入

<?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 name="emp" class="cn.xx.beans.Emp">
        <property name="ename" value="admin"></property>
        <property name="age" value="20"></property>
        <property name="hobby">
            <array>
                <value>play1</value>
                <value>play2</value>
                <value>play3</value>
            </array>
        </property>
    </bean>
</beans>

List类型数据注入

<?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="stu1" class="cn.xx.beans.Student">
        <property name="name" value="admin1"></property>
    </bean>

    <bean id="stu2" class="cn.xx.beans.Student">
        <property name="name" value="admin2"></property>
    </bean>

    <bean id="teacher" class="cn.xx.beans.Teacher">
        <property name="name" value="tom"></property>
        <property name="students">
            <list>
                <ref bean="stu1"></ref>
                <ref bean="stu2"></ref>
            </list>
        </property>
    </bean>
</beans>

Map类型数据注入

<?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="course1" class="cn.xx.beans.Course">
        <property name="name" value="math"></property>
    </bean>

    <bean id="course2" class="cn.xx.beans.Course">
        <property name="name" value="english"></property>
    </bean>

    <bean id="student" class="cn.xx.beans.Student">
        <property name="name" value="tom"></property>
        <property name="courseMap">
           <map>
               <entry key="math">
                   <ref bean="course1"></ref>
               </entry>

               <entry key="en">
                   <ref bean="course2"></ref>
               </entry>
           </map>
        </property>
    </bean>
</beans>

util标签注入List和Map

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       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/util http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="course1" class="cn.xx.beans.Course">
        <property name="name" value="math"></property>
    </bean>

    <bean id="course2" class="cn.xx.beans.Course">
        <property name="name" value="english"></property>
    </bean>

    <bean id="det1" class="cn.xx.beans.StuDetail">
        <property name="address" value="beijing"></property>
        <property name="phone" value="123456789"></property>
    </bean>

    <bean id="det2" class="cn.xx.beans.StuDetail">
        <property name="address" value="tianjing"></property>
        <property name="phone" value="6666666"></property>
    </bean>

    <util:list id="list">
        <ref bean="det1"></ref>
        <ref bean="det2"></ref>
    </util:list>

    <util:map id="map">
        <entry key="ma">
            <ref bean="course1"></ref>
        </entry>

        <entry key="en">
            <ref bean="course2"></ref>
        </entry>
    </util:map>

    <bean id="student" class="cn.xx.beans.Student">
        <property name="name" value="tom"></property>
        <property name="courseMap">
           <ref bean="map"></ref>
        </property>
        <property name="detailList">
            <ref bean="list"></ref>
        </property>
    </bean>
</beans>

p名称空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       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/util http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="course1" class="cn.signp.beans.Course">
        <property name="name" value="math"></property>
    </bean>

    <bean id="course2" class="cn.signp.beans.Course">
        <property name="name" value="english"></property>
    </bean>

    <bean id="det1" class="cn.signp.beans.StuDetail">
        <property name="address" value="beijing"></property>
        <property name="phone" value="123456789"></property>
    </bean>

    <bean id="det2" class="cn.signp.beans.StuDetail">
        <property name="address" value="tianjing"></property>
        <property name="phone" value="6666666"></property>
    </bean>

    <util:list id="list">
        <ref bean="det1"></ref>
        <ref bean="det2"></ref>
    </util:list>

    <util:map id="map">
        <entry key="ma">
            <ref bean="course1"></ref>
        </entry>

        <entry key="en">
            <ref bean="course2"></ref>
        </entry>
    </util:map>

    <bean id="student2" class="cn.signp.beans.Student"
    p:name="tome" p:detailList-ref="list" p:courseMap-ref="map"></bean>

</beans>

引入外部文件属性

<?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/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
    </bean>

</beans>

bean 的作用域

在 Spring 中可以通过配置 bean 标签的 scope 属性来指定 bean 的作用域范围,bean 的默认作用域为 singleton。

取值含义创建对象的时机
singleton在 IOC 容器中,这个 bean 的对象始终为单实例IOC 容器初始化时
prototype这个 bean 在 IOC 容器中有多个实例获取 bean 时

如果是在 WebApplicationContext 环境下还会有另外几个作用域(但不常用)

取值含义
request在一个请求范围内有效
session在一个会话范围内有效

bean 的生命周期

  1. bean 的创建(调用无参构造)
  2. 给 bean 对象设置相关属性
  3. bean 的后置处理器(初始化之前)
  4. bean 对象的初始化(调用指定的初始化方法)
  5. bean 的后置处理器(初始化之后)
  6. bean 对象创建完成,可以使用了
  7. bean 对象销毁(配置指定的销毁方法)

测试对象类 Worker:

package cn.xx.beans;

public class Worker {

    private String name;

    public Worker() {
        System.out.println("1. call constructor");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("2. call setName");
        this.name = name;
    }

    public void initParams(){
        System.out.println("4. call init params method");
    }

    public void destroyParams(){
        System.out.println("7. call destroy params method");
    }

    @Override
    public String toString() {
        return "Worker{" +
                "name='" + name + '\'' +
                '}';
    }
}

在 IOC 容器中配置 bean 的初始化和销毁方法:

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

    <bean id="worker" class="cn.xx.beans.Worker"
    init-method="initParams" destroy-method="destroyParams">
        <property name="name" value="lining"></property>
    </bean>

    <bean id="myBeanPost" class="cn.xx.beans.MyBeanPost"></bean>
</beans>

测试代码:

  @Test
   public void testWorker(){
       ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-life.xml");
       Worker worker = context.getBean("worker", Worker.class);
       System.out.println("6. 对象创建完成 "+ worker);
       context.close();
   }

bean 的后置处理器
bean 的后置处理器会在声明周期的初始化前后添加额外的操作,需要实现 BeanPostProcessor 接口,并且需要配置到 IOC 容器中。需要注意的是 bean 的后置处理器不是单独针对一个 bean 生效,而是针对整个 IOC 容器中的所有 bean 都生效。

创建一个 bean 的后置处理器:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5. bean 的后置处理器(初始化之前)");
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("3. bean 的后置处理器(初始化之后)");
        return bean;
    }
}

配置后置处理器到 IOC 容器中:

<bean id="myBeanPost" class="cn.xx.beans.MyBeanPost"></bean>

FactoryBean

FactoryBean 是 Spring 提供的一种整合第三方框架的常用机制。和普通的 bean 不同,配置了一个 FactoryBean 类型的 bean,在获取 bean 的时候,得到的并不是 class 属性中配置的这个类的对象,而是通过 getObject() 方法返回的值。通过这种机制,Spring 可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只是展示最简洁的界面给我们。
我们整合 Mybatis 时,Spring 就是通过 FactoryBean 机制来帮我们创建 SqlSeesionFactory 对象的。

创建一个 FactoryBean 实现类:

import org.springframework.beans.factory.FactoryBean;

public class MyFactoryBean implements FactoryBean<Worker> {
    @Override
    public Worker getObject() throws Exception {
        return new Worker();
    }

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

在 IOC 容器中注册:

<?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="worker" class="cn.xx.beans.MyFactoryBean"></bean>
</beans>

测试获取 bean:

@Test
public void testFactoryBean() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean-factory-bean.xml");
    Object worker = context.getBean("worker");
    System.out.println(worker);
}

基于XML的自动装配

使用 bean 标签的 autowire 属性进行自动装配属性。
autowire 属性的值:

  • byType
    根据属性类型自动装配,要求容器中必须存在一个与被装配属性类型相同的 bean,如果存在多个,则会抛出异常。
  • byName
    根据属性名自动装配,要求自动装配的属性名称必须与依赖对象的名称一致,即在 XML 中定义的<bean> 标签的 id 属性值或 name 属性值。
  • constructor
    根据构造函数参数类型进行自动装配,要求容器中必须存在一个与构造函数参数类型相同的 bean。

通常来说,应该优先考虑使用 byType 属性进行自动装配,因为这样可以降低耦合度,减少手动配置的工作量。但是,在某些情况下,byName 或 constructor 属性可能更适合。

byType 自动装配的简单使用:

public interface MyController {

    public void addUser();
}
public class MyControllerImpl implements MyController{

    private MyService service;

    public void setService(MyService service) {
        this.service = service;
    }

    @Override
    public void addUser() {
        System.out.println("controller addUser");
        service.addUser();
    }
}
public interface MyService {

    public void addUser();
}
public class MyServiceImpl implements MyService {

    private MyDao dao;

    public void setDao(MyDao dao) {
        this.dao = dao;
    }

    @Override
    public void addUser() {
        System.out.println("service addUser");
        dao.addUser();
    }
}
public interface MyDao {
    public void addUser();
}
public class MyDaoImpl implements MyDao{
    @Override
    public void addUser() {
        System.out.println("Dao addUser");
    }
}

配置文件:

<?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="controller" class="cn.xx.controller.MyControllerImpl" autowire="byType"></bean>

    <bean id="service" class="cn.xx.service.MyServiceImpl" autowire="byType"></bean>

    <bean id="dao" class="cn.xx.dao.MyDaoImpl"></bean>
</beans>

测试代码:

public class TestAutowire {

    @Test
    public void testAutowire()  {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-auto.xml");
        MyControllerImpl controller = context.getBean("controller", MyControllerImpl.class);
        controller.addUser();
    }
}

基于注解的bean注入

Spring 通过注解实现自动装配的步骤

  1. 引入依赖
  2. 开启组件扫描。
  3. 使用自定义注解解析 Bean。
  4. 依赖注入。

Spring 自动扫描

基本使用

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

    <!-- 开启组件扫描 -->
    <context:component-scan base-package="cn.test"></context:component-scan>
    
</beans>

指定需要排除的组件
expression 指定具体排除的注解或者类的全类名
type 设置排除的依据,type 的取值如下:

  • annotation 根据注解排除
  • assignable 根据类型排除
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--指定需要排除组件-->
    <context:component-scan base-package="cn.test">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

仅扫描指定组件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--仅扫码指定组件-->
    <context:component-scan base-package="cn.test" use-default-filters="false">
        <context:include-filter type="assignable" expression="cn.test.User"/>
    </context:component-scan>
</beans>

Spring 会自动扫描以下类型的类:

  1. 带有 @Component、@Service、@Repository、@Controller 注解的类
  2. 实现了特定接口的类,例如实现了 InitializingBean、DisposableBean、ApplicationContextAware、BeanNameAware 等接口的类
  3. 配置类(带有 @Configuration 注解的类),配置类里面的 @Bean 方法所返回的实例也会被自动扫描
  4. 带有 @Endpoint 注解的类,用于定义管理端点的类
  5. 带有 @Enable*** 注解的类,用于开启某个特定功能模块
  6. 其他自定义注解,通过 @Import 注解导入的类

Spring 定义 Bean 的注解

Spring 提供了多个注解,这些注解可以直接标注在 java 类上,将它们定义为 Spring Bean。(或者组件)

注解说明
@Component该注解用于描述 Spring 中的 bean,仅仅表示容器中的一个组件,并且可以作用在应用的任何层次,例如 Service 层、Dao 层,使用是只需要将该注解标注的使用的类之上
@Repository该注解用于将数据库访问层(Dao 层)的类标记为 Spring 中的 Bean,其功能与 @Component 相同
@Service该注解通常用于业务层(Service 层),用于将业务层的类标记为 Spring 中的 Bean,其功能与 @Component 相同
@Controller该注解通常作用在控制层(如 SpringMVC 的 Controller 层),用于将控制层的类标记为 Spring 中的 Bean,其功能与 @Component 相同

@AutoWired 注入

AutoWired 注解可以使用在 构造器、set 方法、构造器形参、属性、注解 上。该注解有一个 required 属性,默认值是 true。表示在注入的时候,要求被注入的 bean 必须是存在的,如果不存在就报错。
如果 required 属性设为 false,表示被注入的 bean 存在时才进行注入,不存在时就不会注入。

属性上注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class MyControllerImpl implements MyController {

    //1.属性上使用 AutoWired 注解
    @Autowired
    private MyService service;

    @Override
    public void addUser() {
        System.out.println("controller addUser");
        service.addUser();
    }
}
set 方法上注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MyControllerImpl implements MyController {
    private MyService service;
    //2.方法上使用 AutoWired 注解
    @Autowired
    public void setService(MyService service) {
        this.service = service;
    }

    @Override
    public void addUser() {
        System.out.println("controller addUser");
        service.addUser();
    }
}
构造器注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MyControllerImpl implements MyController {

    private MyService service;
    //3.构造器上使用 AutoWired 注解
    @Autowired
    public MyControllerImpl(MyService service) {
        this.service = service;
    }

    @Override
    public void addUser() {
        System.out.println("controller addUser");
        service.addUser();
    }
}
构造器形参上注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MyControllerImpl implements MyController {

	private MyService service;
    //4.形参上使用 AutoWired 注解
    public MyControllerImpl(@Autowired MyService service) {
        this.service = service;
    }


    @Override
    public void addUser() {
        System.out.println("controller addUser");
        service.addUser();
    }
}
无注解

当前类声明是为 Spring 组件时,当它只有一个有参构造函数,可以完成自动注入。

import cn.test.dao.MyDao;
import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl implements MyService {

    private MyDao dao;

    public MyServiceImpl(MyDao dao) {
        this.dao = dao;
    }

    @Override
    public void addUser() {
        System.out.println("service addUser");
        dao.addUser();
    }
}
@Qualifier

@Autowired 默认按照类型进行注入,如果存在多个类型相同的 bean,则需要使用 @Qualifier 注解结合使用来指定具体的 bean 名称。

import cn.test.dao.MyDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl implements MyService {

    @Autowired
    @Qualifier("userDaoImpl")
    private MyDao dao;

    @Override
    public void addUser() {
        System.out.println("service addUser");
        dao.addUser();
    }
}

@Resource 注入

@Resource 注解也可以完成属性的注入。它和 @AutoWired 有何不同?

  1. @Resource 是 JSR-250 规范中定义的,属于 JDK 的一部分。而 @Autowired 是 Spring 自己的注解。
  2. @Resource 默认按照名称进行自动装配,可以通过 name 属性指定注入的名称。未指定 name 时,使用属性名作为 name,通过 name 找不到时,会自动启动通过类型的装配。而 @Autowired 默认是按照类型进行注入,可以通过 @Qualifier 指定注入的名称。
  3. @Resource 注解作用在属性上、setter 方法上。而 @AutoWired 注解作用在属性上、setter 方法、构造器、构造器参数上。
  4. @Resource 可以注入任意类型的 bean,包括基础类型和 String 等简单类型;而 @Autowired 只能注入 bean 类型的属性。

@Resource 注解属于 jdk 扩展包,所以不在 jdk 中。需要引入以下依赖(如果是 jdk8 的话不需要额外引入依赖,高于 jdk11 或低于 jdk8 需要引入以下依赖)

<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>
根据 name 进行注入
@Repository("testUserDaoImpl")
public class UserDaoImpl implements UserDao{
    @Override
    public void addUser() {
        System.out.println("User Dao addUser");
    }
}

@Service("testUserServiceImpl")
public class UserServiceImpl implements UserService {

    @Resource(name = "testUserDaoImpl")
    private UserDao dao;


    @Override
    public void addUser() {
        System.out.println("service addUser");
        dao.addUser();
    }
}

@Controller
public class UserControllerImpl implements UserController {

    @Resource(name = "testUserServiceImpl")
    private UserService service;

    @Override
    public void addUser() {
        System.out.println("controller addUser");
        service.addUser();
    }
}
按照属性名来匹配注入
@Service("testUserServiceImpl")
public class UserServiceImpl implements UserService {

    @Resource(name = "testUserDaoImpl")
    private UserDao dao;


    @Override
    public void addUser() {
        System.out.println("service addUser");
        dao.addUser();
    }
}

@Controller
public class UserControllerImpl implements UserController {


    @Resource
    private UserService testUserServiceImpl;
	//UserService 属性名称 testUserServiceImpl,对应 Service 组件中的 value 值

    @Override
    public void addUser() {
        System.out.println("controller addUser");
        testUserServiceImpl.addUser();
    }
}
按照类型进行匹配注入
@Repository
public class UserDaoImpl implements UserDao{
    @Override
    public void addUser() {
        System.out.println("User Dao addUser");
    }
}

@Service("testUserServiceImpl")
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao dao;
	//@Resource 不指定名称,会按照类型来进行匹配注入

    @Override
    public void addUser() {
        System.out.println("service addUser");
        dao.addUser();
    }
}

Spring 全注解开发

Spring 全注解开发指的是不使用 Spring xml 配置文件,使用配置类替换配置文件。

创建一个配置类,并开启组件扫描,指定扫描的包名

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("cn.test")//指定扫描的包名
public class SpringConfig {
}

测试类:

@Test
public void testAuto(){
    ApplicationContext context =
            new AnnotationConfigApplicationContext(SpringConfig.class);
    MyController controller = context.getBean(MyController.class);
    controller.addUser();
}

@Bean 定义一个 bean 对象

在 Spring 中,@Bean 注解用于定义一个 bean 对象,可以用于在 @Configuration 类中声明一个 Bean,也可以用于在 @Component 类中声明一个 Bean。

@Bean 注解将生成一个名为 “myService” 的 bean。返回的对象实例将成为 Spring 应用程序上下文中的 bean。

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

@Bean 注解将生成一个名为 “myService” 的 bean。返回的对象实例将成为 Spring 应用程序上下文中的 bean。

@Component
public class MyService {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

注意:@Bean 注解仅定义一个 bean,但并没有将其添加到应用程序上下文中。要将 bean 添加到应用程序上下文中,可以使用 @ComponentScan 或 @Bean 注解的参数来指定要扫描的包或类。

IOC 自定义实现

动态代理

  • 动态代理分为 JDK 动态代理和 cglib 动态代理
  • 当目标类有接口的情况下使用 JDK 动态代理和 cglib 动态代理,没有接口时只能使用 cglib 动态代理
  • JDK 动态代理动态生成的代理类会在 com.sun.proxy 包下,类名为 $proxy1,和目标类实现相同的接口
  • cglib 动态代理动态生成的代理类会和目标在相同的包下,会继承目标类
  • JDK 的动态代理要求被代理的目标类必须实现接口。因为这个技术要求 代理对象和目标对象实现相同的接口
  • cglib 动态代理通过 继承被代理的目标类 实现的,所以不需要目标类实现接口
  • AspectJ 是 AOP 思想的一种实现,本质上是静态代理,将代理逻辑织入被代理的目标类编译得到的字节码文件中,所以最终效果的动态的。weaver 就是织入器。Spring 只是借用了 AspectJ 中的注解

AOP

  1. 切面(Aspect):横跨多个类的功能被称为切面。切面定义了这些跨越多个类的横切关注点,如事务管理(transaction management)、异常处理(exception handling)等。

  2. 连接点(Join Point):程序执行过程中明确的点,如方法调用、异常处理、字段访问等,称为连接点。

  3. 通知(Advice):在连接点上执行的操作称为通知。通知定义了切面在连接点处执行的操作,通知类型包括“前置通知”、“后置通知”、“环绕通知”、“异常通知”和“最终通知”。

  4. 切入点(Pointcut):切入点是指那些具有相同特征的连接点。在应用中,通常使用表达式来匹配切入点。

  5. 切点(Pointcut):切点是指一个或一组切入点。

  6. 引入(Introduction):引入可以在不修改现有类的情况下,向现有的类添加新方法或属性。

  7. 织入(Weaving):将切面与对象关联起来的过程称为织入。织入可以在编译时、类加载时或运行时完成。

动态代理

使用 Proxy 类实现动态代理

基于注解的 AOP

依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.0.8</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.8</version>
</dependency>

在 xml 中开启 aspectj 自动代理

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

    <context:component-scan base-package="cn.test"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

切入点表达式

execution(modifier? return-type declaring-type?name-pattern(param-pattern) throws-pattern?)

  • modifier
    访问修饰符
  • return-type
    返回值类型。如果想要明确指定一个返回值类型,那么必须同时写明访问修饰符
  • declaring-type
    全限定类名 / 接口。类名和接口名部分可以使用 * 进行通配,如 *Service 可以匹配 LoginService、UserService
  • name-pattern
    方法名。方法名部分使用 * 进行通配。
  • param-pattern
    参数类型
    • 可以使用 (..) 表示任意参数列表
    • 使用 (int,..) 表示一个以 int 类型参数开头的参数列表
    • 基础数据类型和对应的包装类型是不匹配的
  • throws-pattern
    抛出的异常类型

通知类型

  1. 前置通知(@Before)。在被代理的目标方法前执行
  2. 返回通知(@AfterReturning)。在被代理的目标方法成功结束后执行
  3. 后置通知(@After)。在被代理的目标方法最终结束后执行
  4. 异常通知(@AfterThrowing)。在被代理的目标方法异常结束后执行
  5. 环绕通知(@Around)。使用 try-catch-finally 结构围绕整个被代理的目标方法,包括以上四种通知对应的所有位置

基于注解的 AOP 的步骤

  1. 引入 aop 的相关依赖,开启 AOP
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.0.8</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.8</version>
</dependency>

在 xml 中开启 aspectj 自动代理

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

    <context:component-scan base-package="cn.test"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  1. 创建目标资源(1. 接口,2. 实现类)
public interface Calculator {
    int add(int x,int y);
}

@Component
public class CalculatorImpl implements Calculator{
    @Override
    public int add(int x, int y) {
        return x + y;
    }
}
  1. 创建切面类(1. 切入点,2. 通知类型)
package cn.test;


import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect // 切面类
@Component // ioc 组件
public class CalculatorAspect {

    //    @Before("execution(public int cn.test.CalculatorImpl.add(int,int))")
    @Before("execution(public int cn.test.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("--前置通知--");
        System.out.println("被代理方法名称: "+joinPoint.getSignature().getName());
        System.out.println("方法参数: "+ Arrays.toString(joinPoint.getArgs()));
    }

    @After("execution(* cn.test.CalculatorImpl.*(..))")
    public void afterMethod(){
        System.out.println("--后置通知--");
    }
    
	//通过 returning 可以获取方法返回值
	@AfterReturning(value = "execution(* cn.test.CalculatorImpl.*(..))",returning = "result")
	public void returnMethod(JoinPoint joinPoint,Object result){
	    System.out.println("返回值: "+ result);
	    System.out.println("--返回通知--");
	}
	
	@AfterThrowing("execution(* cn.test.CalculatorImpl.*(..))")
	public void exceptionMethod(JoinPoint joinPoint){
	    System.out.println("--异常通知--");
	}
}
  1. 编写测试类
    注意:如果 bean 所在的类被 aop 代理,在获取 bean 时,使用接口的 class 来获取 bean
package cn.test;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopTest {

    @Test
    public void testAdd(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //注意这里获取 bean 时一定要写接口的 class 来获取,因为用到了 aop
        Calculator bean = context.getBean(Calculator.class);
        System.out.println("cal: "+bean.add(5,5));
    }
}

切入点表达式重用

使用 @Pointcut 声明一个切入点表达式,在通知的注解的 value 处直接使用声明 @Pointcut 注解的方法。以此来复用切入点表达式。如果不在同一个切面类中,需要增加包名和类名

   @Before("pointCut()")
   public void beforeMethod2(){
       System.out.println("重用切入点表达式");
   }

	//不在同一个切面类中,需要增加包名和类名
    @Before("cn.test.CalculatorAspect.pointCut()")
    public void afterMethod2(){
        System.out.println("重用切入点表达式");
    }

   @Pointcut("execution(* cn.test.CalculatorImpl.sub(..))")
   public void pointCut(){

   }

切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的 内外嵌套 顺序

  • 优先级高的切面:外面
  • 优先级低的切面:里面
    使用 @Order 注解可以控制切面的优先级
  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

基于 xml 的 aop 通知

public interface Calculator {
    int add(int x,int y);
}

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int x, int y) {
        return x + y;
    }
}

切入面类


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.util.Arrays;

public class CalculatorAspect {


    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("--前置通知--");
        System.out.println("被代理方法名称: "+joinPoint.getSignature().getName());
        System.out.println("方法参数: "+ Arrays.toString(joinPoint.getArgs()));
    }


    public void afterMethod(JoinPoint joinPoint){
        System.out.println("--后置通知--");
        System.out.println("被代理方法名称: "+joinPoint.getSignature().getName());
    }


    public void exceptionMethod(JoinPoint joinPoint){
        System.out.println("--异常通知--");
    }

    public void returnMethod(JoinPoint joinPoint,Object result){
        System.out.println("返回值: "+ result);
        System.out.println("--返回通知--");
    }


    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        System.out.println("环绕前置通知");
        System.out.println("方法名: "+ joinPoint.getSignature().getName());
        System.out.println("方法参数: "+ Arrays.toString(joinPoint.getArgs()));
        Object result = null;
        try {
            result = joinPoint.proceed();
            System.out.println("环绕返回通知");
        }catch (Throwable e) {
            System.out.println("环绕异常通知");
            e.printStackTrace();
        } finally {
            System.out.println("环绕后置通知");
        }
 		return result;
    }

    @Before("pointCut()")
    public void beforeMethod2(){
        System.out.println("重用切入点表达式");
    }

    @Before("cn.xx.CalculatorAspect.pointCut()")
    public void afterMethod2(){
        System.out.println("重用切入点表达式");
    }

    @Pointcut("execution(* cn.test.CalculatorImpl.sub(..))")
    public void pointCut(){

    }
}

测试类

   @Test
   public void testAdd(){
       ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
       Calculator bean = context.getBean(Calculator.class);
       System.out.println("cal: "+bean.add(5,5));
   }

xml 配置 aop 的 5 种通知

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

    <bean id="cal" class="cn.xx.CalculatorImpl"></bean>
    <bean id="calAspect" class="cn.xx.CalculatorAspect"></bean>
    <aop:config>
        <!--配置切面类-->
        <aop:aspect ref="calAspect">
            <!--配置切入点-->
            <aop:pointcut id="pointcut" expression="execution(* cn.xx.CalculatorImpl.add(..))"/>
            <!--前置通知-->
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <!--后置通知-->
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <!--返回通知-->
            <aop:after-returning method="returnMethod" pointcut-ref="pointcut" returning="result"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="exceptionMethod" pointcut-ref="pointcut"></aop:after-throwing>
            <!--环绕通知-->
            <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

Spring 整合 Junit 5

  1. 导入依赖
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.1</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.9</version>
</dependency>
package cn.test;

import org.springframework.stereotype.Component;

@Component
public class User {
    
    public User() {
        System.out.println("user...........");
    }
}

@SpringJUnitConfig(locations = "classpath:spring-junit.xml")
public class SpringJunit5Test {

    @Autowired
    private User user;
    @Test
    public void test(){
        System.out.println(user);
    }
}

Spring 整合 Junit 4

引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.9</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-junit.xml")
public class SpringJunit4Test {

    @Autowired
    private User user;
    @Test
    public void test(){
        System.out.println(user);
    }
}

Spring JdbcTemplate

依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.16</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

配置 DataSource 以及 jdbcTemplate

jdbc 配置文件

jdbc.username=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/pro
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
<?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/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
</beans>

jdbcTemplate 使用

package cn.test;


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import java.util.List;

@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JdbcTempTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testUpdate() {
        String sql = "update t_user set username=?,password=? where id = ?;";
        int i = jdbcTemplate.update(sql, "dsada111", "bbbbb",5);
        System.out.println("updated i: " + i);
    }

    @Test
    public void testCreate(){
        String sql = "insert into t_user (username, password,email) values (?,?,?);";
        int i = jdbcTemplate.update(sql, "rtst", "msjtp2","sada@qq.com");
        System.out.println("created i: " + i);
    }

    @Test
    public void testDelete(){
        String sql = "delete from t_user where id = ?;";
        int i = jdbcTemplate.update(sql, 5);
        System.out.println("delete i: " + i);
    }

    @Test
    public void testQuery1(){
        String sql = "select username,password from t_user where id=?;";
        User u = jdbcTemplate.queryForObject(sql,(resultSet, rowNum) -> {
            User user = new User();
            user.setUsername(resultSet.getString("username"));
            user.setPassword(resultSet.getString("password"));
            return user;
        },1);
        System.out.println(u);
    }

    @Test
    public void testQuery2(){
        String sql = "select id,username,password,email from t_user where id=?;";
        User u = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class),1);
        System.out.println(u);
    }

    @Test
    public void testQueryList1(){
        String sql = "select * from t_user;";
        List<User> users = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(User.class));
        System.out.println(users);
    }
}

jdbcTemplate 声明式事务

  1. 创建测试表
create table t_book (
  book_id int(11) primary key auto_increment comment '主键',
  book_name varchar(20) default null comment '图书名称',
  price double default null comment '价格',
  stock int(10) unsigned default null comment '库存'
);
create table st_user(
    user_id int(11) auto_increment primary key comment '主键',
    username varchar(20) default null comment '用户名',
    balance int(10) unsigned default null comment '余额'
);
  1. 创建 controller
public interface BookController {
    void buyBook(Integer bookId, Integer userId);
    void addBook(Book book);

    void addUser(BookUser user);
}
@Controller
public class BookControllerImpl implements BookController{

    private BookService service;

    @Autowired
    public BookControllerImpl(BookService service) {
        this.service = service;
    }

    @Override
    public void buyBook(Integer bookId, Integer userId) {
        service.buyBook(bookId,userId);
    }

    @Override
    public void addBook(Book book) {
        service.addBook(book);
    }

    @Override
    public void addUser(BookUser user) {
        service.addUser(user);
    }
}
  1. 创建 service
public interface BookService {

    void buyBook(Integer bookId, Integer userId);
    void addBook(Book book);

    void addUser(BookUser user);
}
@Service
public class BookServiceImpl implements BookService{

    private BookDao dao;
    @Autowired
    public BookServiceImpl(BookDao dao) {
        this.dao = dao;
    }

    @Transactional
    @Override
    public void buyBook(Integer bookId, Integer userId) {
        dao.buyBook(bookId,userId);
    }

    @Override
    public void addBook(Book book) {
        dao.addBook(book);
    }

    @Override
    public void addUser(BookUser user) {
        dao.addUser(user);
    }
}
  1. 创建 dao
public interface BookDao {
    void buyBook(Integer bookId, Integer userId);

    void addBook(Book book);

    void addUser(BookUser user);
}

@Repository
public class BookDaoImpl implements BookDao {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public BookDaoImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void buyBook(Integer bookId, Integer userId) {
        Double price = getBookPriceById(bookId);
        if (price != null) {
            int priceInt = price.intValue();
            String bookSql = "update t_book set stock=stock-? where book_id = ?;";
            int i1 = jdbcTemplate.update(bookSql, 1, bookId);
            System.out.println("更新图书库存: " + i1);
            String userSql = "update st_user set balance=balance-? where user_id = ?;";
            int i = jdbcTemplate.update(userSql, priceInt,userId);
            System.out.println("更新用户余额: " + i);
        }
    }

    @Override
    public void addBook(Book book) {
        String sql = "insert into t_book (book_name,price,stock) values (?,?,?);";
        int i1 = jdbcTemplate.update(sql, book.getBook_name(),book.getPrice(),book.getStock());
        System.out.println("添加图书: "+i1);
    }

    @Override
    public void addUser(BookUser user) {
        String sql = "insert into st_user (username,balance) values (?,?);";
        int i1 = jdbcTemplate.update(sql, user.getUsername(),user.getBalance());
        System.out.println("添加用户: "+i1);
    }

    private Double getBookPriceById(Integer bookId) {
        String sql = "select price from t_book where book_id = ?;";
        Double price = jdbcTemplate.queryForObject(sql, Double.class, bookId);
        System.out.println("price: " + price);
        return price;
    }
}

在 xml 中开启事务

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

    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <bean id="tr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    
    <!--开启事务的注解驱动-->
    <!--通过 @Transational 注解所标识的方法或类中的所有方法,都会被事务管理器管理-->
    <tx:annotation-driven transaction-manager="tr"></tx:annotation-driven>
</beans>

@Transational

@Transational 属性

  • readOnly
    该值为 true 时,只读,不能修改

  • timeout
    超时时间,单位为 秒

  • 回滚策略
    声明式事务默认只针对运行时异常回滚,编译时异常不回滚

    1. robackFor 需要设置一个 Class 类型的对象
    2. robackForClassName 需要设置一个字符串类型的全类名
    3. noRobackFor 需要设置一个 Class 类型的对象
    4. noRobackForClassName 需要设置一个字符串类型的全类名
  • 隔离级别(isolution)

    1. 读未提交:READ UNCOMMITTED
      允许 Transaction1 读取 Transaction2 未提交的修改。会有脏读现象。
    2. 读已提交:READ COMMITTED
      要求 Transaction1 只能读取 Transaction2 已提交的修改。会有不可重复读现象。
    3. 可重复读:REPEATABLE READ
      确保 Transaction1 可以多次从一个字段中读取到相同的值,即 Transaction1 执行期间禁止其他事物对这个字段进行更新。会有幻读现象。
    4. 串行化:SERIALIZABLE
      确保 Transaction1 可以多次从一个表中读到相同的行,在 Transaction1 执行期间。禁止其他事务对这个表进行添加、更新、删除,可以避免任何并发问题。但性能低下。
  • 传播行为
    事务的传播行为指的是事务在多个操作之间传播的方式,即在多个操作中如何保持事务的一致性和可靠性。常见的事务传播行为包括:

  1. REQUIRED:默认的传播行为。如果当前没有事务,则创建一个新的事务;如果已经存在事务,则加入该事务;

  2. REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务并在其中执行操作。如果当前存在事务,则将它挂起,等待新创建的事务完成后再恢复;

  3. SUPPORTS:如果当前存在事务,则在该事务中执行操作;否则,不使用事务执行操作;

  4. NOT_SUPPORTED:禁用事务,并以非事务方式执行操作;

  5. MANDATORY:要求当前存在事务,并在该事务中执行操作。如果当前不存在事务,则抛出异常;

  6. NEVER:禁止在事务中执行操作;

  7. NESTED:在当前事务的嵌套事务中执行操作。如果当前不存在事务,则创建一个新的事务并在其中执行操作。嵌套事务可以独立提交或回滚,也可以与外层事务一起提交或回滚。

各个隔离级别解决并发问题能力

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

脏读,幻读,不可重复读
脏读(Dirty read)指的是一个事务读取了另一个未提交的事务中的数据,导致读取到了不正确的数据。

幻读(Phantom read)指的是在一个事务中,两次相同的查询可能会返回不同的结果,因为在这两次查询之间,有可能会有另一个事务插入了新的数据。

不可重复读(Non-repeatable read)指的是在一个事务中,两次相同的查询可能会返回不同的结果,因为在这两次查询之间,有可能会有另一个事务修改了数据。

全注解的声明式事务

创建一个 Spring 配置类来替代 xml 配置文件

package cn.test.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@ComponentScan("cn.test")
@Configuration
@EnableTransactionManagement //开启事务管理器
public class SpringConfig {

    @Bean
    public DataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/pro");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }


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

基于 XML 的声明式事务

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--开启组件扫描-->
<context:component-scan base-package="cn.xx.buy"></context:component-scan>

<!--德鲁伊数据源配置-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>

<!--jdbcTemplate对象配置-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="druidDataSource"></property>
</bean>

<!--事务管理器-->
<bean id="tr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"></property>
</bean>

<!--配置事务的增强-->
<tx:advice id="txAdvice" transaction-manager="tr">
	<tx:attributes>
		<tx:method name="get*" read-only="true"/>
		<tx:method name="buy*" read-only="false" propagation="REQUIRED"/>
	<tx:attributes/>
<tx:advice/>

<!--配置切入点和通知使用的方法-->
<aop:config>	
	<aop:pointcut id="pt" expression="execution(* cn.xx.*.*(..))"/>
	<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
</beans>

Spring Resource

Java 标准的 java.net.URL 类和各种 URL 前缀的标准程序无法满足所有对 low-level 资源的访问,比如标准化的 URL 实现可以用于访问需要从类路径或者相对于 ServletContext 获取的资源,并且缺少 Spring 某些所需要的功能,例如检测某资源是否存在等。 而 Spring 的 Resource 声明了访问 low-level 资源的能力。

UrlResource

Resource 的一个实现类,用于访问网络资源,它支持 URL 的绝对路径。

  • http 用于访问 http 协议的网络资源
  • ftp 用于访问 ftp 协议的网络资源
  • file 用于访问文件系统中的资源
try {
    UrlResource urlResource = new UrlResource("http://localhost:8080/test.txt");
    String description = urlResource.getDescription();
    String filename = urlResource.getFilename();
    URL url = urlResource.getURL();
    InputStream inputStream = urlResource.getInputStream();
} catch (MalformedURLException e) {
    throw new RuntimeException(e);
}

ClassPathResource

ClassPathResource 用于访问 classpath 中的资源,需要指定资源所在的包路径和资源名称。

ClassPathResource classPathResource = new ClassPathResource("test.txt");
String description = classPathResource.getDescription();
String filename = classPathResource.getFilename();
String path = classPathResource.getPath();
try {
    URL url = classPathResource.getURL();
    InputStream inputStream = classPathResource.getInputStream();
} catch (IOException e) {
    throw new RuntimeException(e);
}

FileSystemResource

FileSystemResource 用于访问本地文件系统中的资源,可以使用绝对路径或相对路径指定资源的位置。

FileSystemResource fileSystemResource = new FileSystemResource("D:\\test.txt");
String description = fileSystemResource.getDescription();
String filename = fileSystemResource.getFilename();
String path = fileSystemResource.getPath();
try {
    URL url = fileSystemResource.getURL();
    InputStream inputStream = fileSystemResource.getInputStream();
} catch (IOException e) {
    throw new RuntimeException(e);
}

ResourceLoader接口

ResourceLoader 介绍

Spring 提供如下两个标志性接口:

  • ResourceLoader: 该接口实现类的实例可以获得一个 Resource 实例。
  • ResourceLoaderAware: 该接口实现类的实例将获得一个 ResourceLoader 的引用。

在 ResourceLoader 接口里有如下方法:
Resource getResource(String location)
该接口仅有这个方法,用于返回一个 Resource 实例。
ApplicationContext 实现类都实现了 ResourceLoader 接口,因此 ApplicationContext 可直接获取 Resource 实例

ResourceLoader总结

Spring 采用和 ApplicationContext 相同的策略来访问资源。也就是说,
如果 ApplicationContext 实际类型是 FileSystemXmlApplicationContext ,res 就是 FileSystemResource 的实例;
如果 ApplicationContext 实际类型是 ClassPathXmlApplicationContext ,res 就是 ClassPathResource 的实例。
当 Spring 应用需要进行资源访问时,实际上并不需要直接使用 Resource 实现类,而是调用 ResourceLoader 实例的 getResource() 方法来获得资源,ResourceLoader 会负责选择 Resource 实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来。
另外,使用 ApplicationContext 访问资源时,可以通过不同前缀指定强制使用指定的 ClassPathResource、FileSystemResource 等实现类

Resource res1 = cxt.getResource("classpath:bean.xml");
Resource res2 = cxt.getResource("file:bean.xml");
Resource res3 = cxt.getResource("http://localhost:8080/bean.xml");

ResourceLoaderAware接口

ResourceLoaderAware 接口类的实例将获得一个 ResourceLoader 的引用,ResourceLoaderAware 接口也提供了一个 setResourceLoader() 方法,该方法由 Spring 容器负责调用,Spring 容器会将一个 ResourceLoader 作为该方法的参数传入。
如果把实现了 ResourceLoaderAware 接口的 Bean 类放入 Spring 容器中,Spring 容器会将自身当成 ResourceLoader,并作为 setResourceLoader() 方法的参数进入注入。由于 ApplicationContext 的实现类都实现了 ResourceLoader 接口,Spring 容器自身完全可以作为 ResourceLoader 使用。

Resource 作为属性注入到 Spring 容器

import org.springframework.core.io.Resource;

public class ResourceBean {

    private Resource resource;

    public Resource getResource() {
        return resource;
    }

    public void setResource(Resource resource) {
        this.resource = resource;
    }
}
<?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="resource" class="cn.lk.ResourceBean">
        <property name="resource" value="classpath:test.txt"></property>
    </bean>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
ResourceBean resource = context.getBean("resource", ResourceBean.class);
System.out.println( resource.getResource().getDescription());

应用程序上下文和资源路径

不管以怎样的方式创建 ApplicationContext 实例,都需要为 ApplicationContext 指定配置文件,Spring 允许使用一份或多份 XML 配置文件。当程序创建 ApplicationContext 实例是,通常是以 Resource 的方式来访问配置文件的,所以 ApplicationContext 完全支持 ClassPathResourceContext、FileSystemResource、ServletContextResource 等资源访问方式。

ApplicationContext 确定资源访问策略通常有两种方法:

  1. 使用 ApplicationContext 实现类指定访问策略
  2. 使用指定前缀指定访问策略

ApplicationContext 实现类指定访问策略

创建 ApplicationContext 时,通常可以使用以下实现类:

  1. ClassPathXmlApplicationContext:对应使用 ClassPathResource 进行资源访问
  2. FileSystemXmlApplicationContext:对应使用 FileSystemResource 进行资源访问
  3. XmlWebApplicationContext:对应使用 ServletContextResource 进行资源访问

使用前缀指定访问策略

使用通配符加载多个 spring 配置文件
classpath*: 前缀提供了加载多个 xml 配置文件的能力,当使用 classpath*: 前缀指定 xml 配置文件时,系统将搜索类加载路径,找到所有与文件名匹配的文件,分别加载文件中的配置定义,最后合并成为一个 ApplicationContext

ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:bean.xml");

以下会加载以 bean 开头的所有 xml 配置文件

ApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean*.xml");

Validation 数据校验

通过实现 Validator 接口实现

  1. 引入依赖
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>7.0.5.Final</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>4.0.2</version>
</dependency>
  1. 创建需要被验证的类
public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

  1. 创建校验器类
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

public class PersonValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return Person.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors,"name","name.empty","name must not be empty");
        Person person = (Person) target;
        if (person.getAge() < 0 || person.getAge() > 200){
            errors.rejectValue("age","age.outOfRange","age must be in range [0,200]");
        }
    }
}

support 方法用来表示此校验用在哪个类型。
validate 方法是设置校验逻辑的地点,其中 ValidationUtils,是 Spring 封装的校验工具类,帮助快速实现校验。

  1. 测试
public void testValidator(){
    //1.创建 Person 对象
    Person person = new Person();
    person.setName("jack");
    person.setAge(500);
    //2.创建 Person 对应的 DataBinder
    DataBinder dataBinder = new DataBinder(person);
    //3.设置校验器
    dataBinder.setValidator(new PersonValidator());
    //4.调用校验方法
    dataBinder.validate();
    //5.输出校验结果
    BindingResult bindingResult = dataBinder.getBindingResult();
    System.out.println(bindingResult.getAllErrors());
}

通过 Validator 注解实现

  1. 创建 spring 配置类,在类中创建 LocalValidatorFactoryBean
  2. 创建验证类
  3. 使用 validator 注解修饰验证类的属性

常用注解

  • @NotNull 限制必须不能为 null
  • @NotEmpty 只作用于字符串类型,字符串不能为空,并且长度不为0
  • @NotBlank 只作用于字符串类型,字符串不能为空,并且 trim() 后不为空串
  • @DecimalMax(value) 限制必须为一个不大于指定值的数字
  • @DecimalMin(value) 限制必须为一个不小于指定值的数字
  • @Max(value) 限制必须为一个不大于指定值的数字
  • @Min(value) 限制必须为一个不小于指定值的数字
  • @Pattern(value) 限制必须符合指定的正则表达式
  • @Size(max,min) 限制字符串长度必须在 min 和 max 之间
  • @Email 验证注解的元素值是 Email,也可以通过正则表达式和 flag 指定自定义的 email 格式

定义配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@ComponentScan("cn.test")
@Configuration
public class ValidatorConfig {
    @Bean
    public LocalValidatorFactoryBean getLocalValidatorFactoryBean(){
        return new LocalValidatorFactoryBean();
    }
}

使用原生校验器

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;

@Service
public class Validator1 {


    private Validator validator;

    @Autowired
    public Validator1(Validator validator) {
        this.validator = validator;
    }

    public boolean validate(User user) {
        Set<ConstraintViolation<User>> validate = validator.validate(user);
        return validate.isEmpty();
    }
}

使用 Spring 校验器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;

import java.util.List;

@Service
public class Validator2 {


    private Validator validator;

    @Autowired
    public Validator2(Validator validator) {
        this.validator = validator;
    }

    public boolean validate(User user) {
        BindException bindException = new BindException(user,user.getName());
        validator.validate(user,bindException);
        List<ObjectError> allErrors = bindException.getAllErrors();
        System.out.println(allErrors);
        return bindException.hasErrors();
    }
}

测试类

@Test
public void testValidator1(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ValidatorConfig.class);
    Validator1 bean1 = context.getBean(Validator1.class);
    User user = new User();
    user.setAge(-1);
    user.setName("root");
    boolean b = bean1.validate(user);
    System.out.println("b = " + b);
}
@Test
public void testValidator2(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ValidatorConfig.class);
    Validator2 bean2 = context.getBean(Validator2.class);
    User user = new User();
      user.setAge(-1);
    user.setName("root");
    boolean b = bean2.validate(user);
    System.out.println("b = " + b);
}

基于方法的校验

使用 MethodValidationPostProcessor 进行校验

  1. 创建配置类,并创建 MethodValidationPostProcessor 对象
@ComponentScan("cn.test")
@Configuration
public class ValidatorConfig2 {


    @Bean
    public MethodValidationPostProcessor getMethodValidationPostProcessor(){
        return new MethodValidationPostProcessor();
    }
}
  1. 创建方法校验类
    在方法上使用 @Vlaid 注解来标注需要验证的对象
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Service
@Validated
public class UserMethodValidator {

    public String testMethod(@NotNull @Valid User user) {
        return user.toString();
    }
}
  1. 编写待验证类
import jakarta.validation.constraints.*;

public class User {

    @NotEmpty
    private String name;

    @Max(200)
    @Min(0)
    private int age;


    @Pattern(regexp = "^1[34578]\\d{9}$")
    @NotBlank(message = "手机号不能为空!")
    private String phone;

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }
}

  1. 测试类
@Test
public void testValidator3(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ValidatorConfig2.class);
    UserMethodValidator bean = context.getBean(UserMethodValidator.class);
    User user = new User();
    user.setAge(500);
      user.setName("root");
    System.out.println("user = " + bean.testMethod(user));
}

继承 ConstraintValidator 实现自定义校验

ConstraintValidator 是 Java Bean Validation(JSR 380)框架中用于验证注解约束的接口。它允许开发人员自定义验证注解的行为,并提供了灵活性和可扩展性。

下面是使用 ConstraintValidator 的步骤:

  1. 创建自定义注解

定义自己的注解,注解用于指定验证的条件。

@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
    String message() default "MyConstraint failed";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
  1. 创建 ConstraintValidator 实现类

创建 ConstraintValidator 接口的实现类,该类使用注解中指定的规则执行验证。

public class MyConstraintValidator implements ConstraintValidator<MyConstraint, String> {
    @Override
    public void initialize(MyConstraint constraintAnnotation) {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        return value.startsWith("prefix_");
    }
}
  1. 在需要验证的对象中使用注解

在需要验证的对象中使用注解 @MyConstraint。

public class MyEntity {
    @MyConstraint
    private String name;
    //...
}
  1. 开始验证

使用 Validator 对象进行对象属性的验证。

public class MyService {
    @Autowired
    private Validator validator;

    public void createMyEntity(MyEntity entity) {
        Set<ConstraintViolation<MyEntity>> violations = validator.validate(entity);
        if (!violations.isEmpty()) {
            throw new ConstraintViolationException(violations);
        }
        //...
    }
}

以上是使用 ConstraintValidator 的基本步骤。需要注意的是,ConstraintValidator 可以接收其它参数来执行更复杂的验证,如通过注入 Spring 的 Bean 进行验证等。

AOT

Aot 特点
AOT全称为Ahead of Time,即“静态编译”,在应用程序运行之前将代码编译成二进制文件。
内存占用低,启动速度快,可以无需 runtime 运行,直接将 runtime 静态链接至最终的程序中,但是无运行时性能加成,不能根据程序运行情况做进一步的优化

JIT 特点
JIT 全称为 Just in Time,即“即时编译”,在应用程序运行时将代码编译成机器码,优点在于占用更少的存储空间和内存。
吞吐量高,有运行时性能加成,可以跑得更快,并可以做到动态生成代码等,但是相对启动速度较慢,并需要一定时间和调用频率才能触发 JIT 的分层机制。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值