Spring框架

Spring框架是企业开发复杂性的一站式解决方案。

Spring框架的核心是IoC容器AOP面向切面编程

Spring框架组成模块

IoC控制反转

由代理人来创建管理对象,消费者通过代理人来获取对象。

IoC的目的是降低对象之间直接耦合。基于配置提高应用程序的可维护性扩展性

  • XML配置文件

    1
    2
    3
    4
    5
    6
    7
    <?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
    https://www.springframework.org/schema/beans/spring-beans.xsd">

    </beans>
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class SpringTest {
    public static void main(String[] args) {
    // 加载Spring XML配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    // 获取对象
    Person lili = context.getBean("liliPerson", Person.class);
    lili.eat();
    }
    }
  • Bean的三种配置方式

    1. 基于XML配置Bean

      1
      2
      3
      4
      5
      <bean id="sweetApple" class="com.codewhale.entity.Apple">
      <property name="name" value="红富士"/>
      <property name="origin" value="中国陕西"/>
      <property name="color" value="红色"/>
      </bean>
      1
      2
      3
      // XML方式创建IoC容器
      // 创建IoC容器并根据配置文件创建对象
      ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
      实例化Bean的三种方式
      1. 基于构造方法对象实例化

        1
        2
        3
        4
        5
        6
        <bean id="sweetApple" class="com.codewhale.entity.Apple">
        <!-- 没有Constructor-arg则代表调用默认构造方法实例化 -->
        <constructor-arg name="name" value="红富士"/>
        <constructor-arg name="origin" value="中国陕西"/>
        <constructor-arg name="color" value="红色"/>
        </bean>

        利用构造方法参数位置实例化

        1
        2
        3
        4
        5
        6
        <bean id="sweetApple" class="com.codewhale.entity.Apple">
        <!-- 利用构造方法参数位置实现对象实例化 -->
        <constructor-arg index="0" value="红富士"/>
        <constructor-arg index="1" value="中国陕西"/>
        <constructor-arg index="2" value="红色"/>
        </bean>
      2. 基于静态工厂实例化

        静态工厂通过静态方法创建对象,隐藏创建对象的细节。

        1
        2
        <bean id="apple1" class="com.codewhale.factory.AppleStaticFactory"
        factory-method="createSweetApple"/>
      3. 基于工厂实例方法实例化

        工厂实例方法创建对象是指IoC容器对工厂类进行实例化并调用对应的实例方法创建对象的过程。

        1
        2
        3
        <bean id="factoryInstance" class="com.codewhale.factory.AppleStaticFactoryInstance"/>

        <bean id="apple2" factory-bean="factoryInstance" factory-method="createSweetApple"/>
      从IOC容器获取bean
      1
      2
      3
      Person lili = context.getBean("liliPerson", Person.class);
      // 或者
      Person lili = (Person)context.getBean("liliPerson");
      bean标签中id与name属性相同点
      • bean id与name都是设置对象在IoC容器中唯一标识
      • 两者在同一个配置文件中都不允许出现重复
      • 两者允许在多个配置文件中出现重复,新对象覆盖旧对象
      bean标签中id与name属性区别
      • id要求更为严格,一次只能定义一个对象标识(推荐)
      • name更为宽松,一次允许定义多个对象标识
      • 都按照驼峰命名书写
    2. 基于注解配置Bean

    3. 基于Java代码配置Bean

DI依赖注入

DI(Dependency Injection)是具体技术实现,是微观实现。

DI在Java中利用反射技术实现对象注入。

注入的两种形式

  1. 基于setter方法注入对象

    利用setter实现静态数值注入

    1
    2
    3
    4
    5
    6
    <bean id="sweetApple" class="com.codewhale.entity.Apple">
    <!-- IoC容器自动利用反射机制运行时调用setXXX方法为属性赋值 -->
    <property name="name" value="红富士"/>
    <property name="origin" value="中国陕西"/>
    <property name="color" value="红色"/>
    </bean>

    利用setter实现对象注入

    1
    2
    3
    4
    5
    <bean id="liliPerson" class="com.codewhale.entity.Person">
    <property name="name" value="莉莉"/>
    <!-- 利用ref注入依赖对象 -->
    <property name="apple" ref="sweetApple"/>
    </bean>
  2. 基于构造方法注入对象

    1
    2
    3
    4
    5
    <bean id="sweetApple" class="com.codewhale.entity.Apple">
    <constructor-arg name="name" value="红富士"/>
    <constructor-arg name="origin" value="中国陕西"/>
    <constructor-arg name="color" value="红色"/>
    </bean>

注入集合对象

  1. 注入List

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="..." class="...">
    <property name="someList">
    <list>
    <value>具体值</value>
    <ref bean="beanId"></ref>
    </list>
    </property>
    </bean>
  2. 注入Set

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="..." class="...">
    <property name="someSet">
    <set>
    <value>具体值</value>
    <ref bean="beanId"></ref>
    </set>
    </property>
    </bean>
  3. 注入Map

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="..." class="...">
    <property name="someMap">
    <map>
    <entry key="k1" value="v1"></entry>
    <entry key="k2" value-ref="beanId"></entry>
    </map>
    </property>
    </bean>
  4. 注入Properties

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="..." class="...">
    <property name="someProperties">
    <props>
    <prop key="k1">v1</prop>
    <prop key="k2">v2</prop>
    </props>
    </property>
    </bean>

Bean对象的作用域及生命周期

  • bean scope属性

    • bean scope属性用于决定对象何时被创建与作用范围

    • bean scope配置将影响容器内对象的数量

    • 默认情况下bean会在IoC容器创建后自动实例化,全局唯一

    • 属性清单

      ![bean scope属性清单](/res/bean scope属性清单.png)

      1. singleton在容器中是单例多线程执行,存在线程安全风险

      2. prototype在容器中多实例,占用更多资源,不存在线程安全问题。

      3. singleton与prototype对比

        singleton与prototype对比

    1
    <bean id="..." class="..." scope="prototype"/>
  • bean生命周期

    bean生命周期

    • init方法使用

      XML配置文件中init-method方法名与该类方法名一致。

      1
      <bean id="..." class="..." scope="prototype" init-method="init"/>
      1
      2
      3
      public void init(){

      }
    • 销毁方法使用

      XML配置文件中destroy-method方法名与该类方法名一致。

      1
      <bean id="..." class="..." scope="prototype" init-method="init" destroy-method="destroy"/>
      1
      2
      3
      public void destroy(){

      }

基于注解与Java Config配置IoC容器

  • 基于注解配置IoC容器

    开启组件扫描

    1
    2
    3
    4
    5
    <!--XML配置开启组件扫描,才能使用注解-->
    <context:component-scan base-package="com.codewhale">
    <!-- 通过正则表达式排除符合规则包(不被实例化和管理) -->
    <context:exclude-filter type="regex" expression="com.codewhale.exl.*" />
    </context:component-scan>
    1. 优势

      • 摆脱繁琐的XML形式的bean与依赖注入配置
      • 基于”声明式”的原则,更适合轻量级的现代企业应用
      • 让代码可读性变得更好,研发人员拥有更好的开发体验
    2. 三类注解

      • 组件类型注解-声明当前类的功能与职责
      • 自动装配注解-根据属性特征自动注入对象
      • 元数据注解-更细化的辅助IoC容器管理对象的注解
    3. 组件类型注解(四种)

      四种组件类型注解

    4. 两类自动装配注解

      两类自动装配注解

      • @Autowired

        放在实例化对象上:Spring Ioc容器会自动通过反射技术将属性private修饰符自动改为public,直接进行赋值,不再执行set方法。

        放在set方法上:自动按类型/名称对set方法参数进行注入。

      • @Resource(推荐使用)
        1. @Resource设置name属性,则按name在Ioc容器中将bean注

        2. @Resource未设置name属性

          2.1 以属性名作为beanname在Ioc容器中匹配bean,如有匹配则注入
          2.2 按属性名未匹配,则按类型进行匹配,同@Autowired,需加入@Primary解决类型冲突
          使用建议:在使用@Resource对象时推荐设置name或保证属性名与bean名称一致

    5. 其他元数据注解

      元数据注解

      通过@Value注解获取config.properties中的数据

      1
      2
      3
      <!--通知Spring IoC容器初始化时加载属性文件-->
      <context:property-placeholder location="classpath:
      config.properties"/>
      1
      2
      @Value("${metaData}") // 读取config.properties的mataData属性值
      private String mataData;
  • 基于Java Config配置IoC容器

    ![Java Config核心注解](/res/Java Config核心注解.png)

    • 优势

      • 完全摆脱XML的束缚,使用独立Java类管理对象与依赖
      • 注解配置相对分散,利用JavaConfig可对配置集中管理
      • 可以在编译时进行依赖检查,不容易出错

Spring AOP面向切面编程

AOP的做法是将通用与业务无关的功能抽象封装为切面类

目的是在不修改源码的情况下对程序行为进行扩展。

Spring AOP底层依赖:aspectjweaver。在maven中需引用“aspectjweaver”依赖。

![Spring AOP中名词解释](/res/Spring AOP中名词解释.png)

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="methodAspect" class="com.codewhale.aspect.MethodAspect"></bean>

<aop:config>
<!-- PointCut切点,使用execution表达式描述切面的作用范围 -->
<!-- execution(public*com.codewhale..*.*(..))说明切面作用在com.codewhale包下的所有类的所有方法 -->
<aop:pointcut id="pointcut" expression="execution(public * com.codewhale..*.*(..))"></aop:pointcut>
<!-- 定义切面类 -->
<aop:aspect ref="methodAspect">
<!-- before(前置)通知(Advice),代表在目标方法运行前先执行methodAspect.printExecutionTime() -->
<aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
  • Spring AOP与AspectJ的关系

    • EclipseAspect是一种基于Java平台的面向切面编程的语言
    • SpringAOP使用AspectJWeaver实现类与方法匹配
    • SpringAOP利用代理模式实现对象运行时功能扩展
  • AOP配置过程

  1. 依赖AspectJ
  2. 实现切面类/方法
  3. 配置Aspect Bean
  4. 配置PointCut
  5. 配置Advice
  • JoinPoint核心方法

    JoinPoint核心方法

  • PointCut切点表达式

    PointCut切点表  达式

  • 五种通知类型

    五种通知类型

    • 环绕通知

      1. 切面类定义

        1
        2
        3
        4
        5
        6
        7
        8
        9
        //ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
        public Object check(ProceedingJoinPoint pjp){
        try {
        Object ret = pjp·proceed();//执行目标方法
        return ret;
        } catch (Throwable throwable){
        thow throwable;
        }
        }
      2. XML配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <bean id="methodchecker" class="com.codewhale.aspect.MethodChecker"></bean>

        <aop:config>
        <aop:pointcut id="pointcut" expression="execution(public * com.codewhale..*.*(..))"></aop:pointcut>
        <!-- 定义切面类 -->
        <aop:aspect ref="methodchecker">
        <!-- 环绕通知 -->
        <aop:around method="check" pointcut-ref="pointcut"/>
        </aop:aspect>
        </aop:config>
  • 基于注解开发Spring AOP

    1
    2
    3
    4
    <!--XML配置开启组件扫描,才能使用注解-->
    <context:component-scan base-package="com.codewhale"/>
    <!--启用Spring AOP注解模式-->
    <aop:aspectj-autoproxy/>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Component // 标记当前类为组件
    @Aspect //说明当前类是切面类
    public class MethodChecker{
    // 环绕通知,参数为PointCut切点表达式
    @Around("execution(* com.codewhale..*.*(..))")
    public Object check(ProceedingJoinPoint pjp) throws Thorwable {

    }
    }
  • Spring AOP实现原理

    1. Spring基于代理模式实现功能动态扩展,包含两种形式:
    2. 目标类拥有接口,通过JDK动态代理实现功能扩展
    3. 目标类没有接口,通过CGLib组件实现功能扩展(通过继承的方式)
    • 代理模式

      代理模式通过代理对象对原对象的实现功能扩展。

    • CGLib实现代理类

      1. CGLib是运行时字节码增强技术
      2. Spring AOP扩展无接口 类使用CGLib
      3. AOP会运行时生成目标继承类字节码的方式进行行为扩展

Spring JDBC与事务管理

Spring JDBC是Spring框架用于处理关系型数据库的模块。
Spring JDBC对JDBCAPI进行封装,极大简化开发工作量。
JdbcTemplate是SpringJDBC核心类,提供数据CRUD方法。

  • 使用步骤

    1. Maven工程引入依赖spring-jdbc

    2. applicationContext.xml配置DataSource数据源

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <!-- 数据源 -->
      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://localhost:3306/whale"/>
      <property name="username" value="root"/>
      <property name="password" value="root"/>
      </bean>

      <!-- JdbcTemplate提供数据CRUD的API -->
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
      <property name="dataSource" ref="dataSource"/>
      </bean>

      <bean id="employeeDao" class="com.codewhale.dao.EmployeeDao">
      <!-- 为Dao注入jdbcTemplate对象 -->
      <property name="jdbcTemplate" ref="jdbcTemplate"/>
      </bean>
    3. 在Dao注入JdbcTemplate对象,实现数据CRUD

      1
      2
      3
      4
      5
      6
      7
      8
      public class EmployeeDao{
      private JdbcTemplate jdbcTemplate;
      public Employee findById(Interger eno){
      String sql = "select * from employee where eno = ?";
      Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class));
      return employee;
      }
      }
  • 编程式事务

    事务是以一种可靠的、一致的方式,访问和操作数据库的程序单元。
    说人话:要么把事情做完,要么什么都不做,不要做一半。
    事务依赖于数据库实现,MySQL通过事务区作为数据缓冲地带。

    • 编程式事务是指通过代码手动提交回滚事务的事务控制方法

    • Spring JDBC通过TransactionManager事务管理器实现事务控制

    • 事务管理器提供commit/rollback方法进行事务提交与回滚

    • 使用事务时需引用aspectj

    • 编程式事务的使用

      1
      2
      3
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
      </bean>
      1
      2
      3
      4
      5
      6
      7
      private DataSourceTransactionManager transactionManager;
      public void batchImport(){
      //定义了事务默认的标准配置
      TransactionDefinition definition = new DefaultTransactionDefinition();
      //开始一个事务,返回事务状态,事务状态说明当前事务的执行阶段
      TransactionStatus status=transactionManager.getTransaction(definition);
      }
  • 声明式事务

    声明式事务指在不修改源码情况下通过配置形式自动实现事务控制,声明式事务本质就是AOP环绕通知。

    当目标方法执行成功时,自动提交事务。

    当目标方法抛出运行时异常时,自动事务回滚。

    • 配置过程

      1. 配置TransactionManager事务管理器

        1
        2
        3
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
        </bean>
      2. 配置事务通知与事务属性

        1
        2
        3
        4
        5
        6
        7
        8
        9
        <!-- 事务通知配置,决定哪些方法使用事务,哪些方法不使用事务 -->
        <tx:advice id="txAdvice" transaction-manager="tansactionManager">
        <tx:attributes>
        <!-- 目标方法名为batchImport时,启用声明式事务,成功提交,运行时异常回滚 -->
        <tx:method name="batchImport" propagation="REQUIRED"/>
        <!-- 设置所有findXXX方法不需要使用事务 -->
        <tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
        </tx:attributes>
        </tx:advice>
      3. 为事务通知绑定PointCut切点

        1
        2
        3
        4
        5
        <!-- 定义声明式事务的作用范围 -->
        <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.codewhale..*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
        </aop:config>
    • 事务传播行为

      事务传播行为是指多个拥有事务的方法在嵌套调用时的事务控制方式
      XML:<tx:method name=”.. “ propagation=”REQUIRED”/>
      注解:@Transactional(propagation=Propagation.REQUIRED)

      • 事务传播行为七种类型
    • 注解形式声明事务

      • 启用注解形式声明式事务

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        <!-- 配置扫包 -->
        <context:component-scan base-package="com.codewhale"/>

        <!-- 配置数据源 -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/whale"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        </bean>

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

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

        <!-- 启用注解形式声明式事务 -->
        <tx:annotation-driven transction-manager="transactionManager"/>
      • 使用注解使用事务

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        @Service
        //声明式事务核心注解
        //放在类上,将声明式事务配置应用于当前类所有方法,默认事务传播为 REQUIRED
        @Transactional
        public class XxxService{
        @Transactional(propagation="Propagation.NOT_SUPPORTED" readOnly=true)
        public Person findPerson(){

        }
        }