0xCAFEBABE

talk is cheap show me the code

0%

spring笔记

practice makes perfect

学习spring也有一段时间了 只是一直没有一个系统的笔记 也没有写markdown的习惯, 学习笔记都是零碎地附着在代码之上的 现在就来系统地整理一下吧

spring是java平台可以说是最牛逼的一个框架之一 如果写程序就像盖房子 那么spring就相当于为你搭好了地基 你要做的只是在这个地基之上添砖加瓦 专注于自己的业务逻辑就行了 剩下的 spring都帮你安排好了

IOC 容器

IOC 即控制反转 即控制权反转 比如在java中 对象的创建的 对象之间的依赖关系 都有程序员掌控生死 当控制反转之后 程序员对对象的控制权就交给了别人 在spring中 就是IOC容器掌管着对象的生命周期 光有IOC容器还不行 还需要有一个东西 告诉 IOC容器怎么管理对象 这个东西就是xml配置文件 统称为 bean.xml

IOC容器通过读取bean.xml 中的bean配置信息来管理对象

bean.xml大概长这样

1
2
3
4
5
6
7
8
9
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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"></beans>

Bean

在spring的世界 一个个的对象就是一个个的bean 相当于对象的组装可在bean.xml中 通过bean 标签配置bean 相当于对象的组装图纸 告诉 IOC容器怎么创建对象

基础bean

1
2
3
4
5
6
7
8
9
<!-- 
name: bean 名称
class: bean 所属的类
lazy-init: 是否懒加载
scope: bean作用域 sington 单例(默认) | prototype 多实例 (每次获取都出创建一个新的) | request 一次请求(web) | session 一次会话(web)
primary: true | false 当存在多个同类型的bean竞争注入时 标为首选bean
-->
<bean name="user" class="com.yong.domain.User">
</bean>

抽象bean

可将bean定义为抽象bean 无法通过ioc容器获取 只能被其他bean继承

继承抽象bean将获得其属性

1
2
3
4
5
<bean id="abstractCar" class="com.yong.bean.Car" abstract="true"/>
<!--继承抽象bean-->
<bean id="car" lazy-init="true" class="com.yong.bean.Car" parent="abstractCar" >
<property name="name" value="AUDI"/>
</bean>

集合bean

spring内置了多种集合bean

需要导入util名称空间

  • map 键值对

    1
    2
    3
    <util:map id="map">
    <entry key="Hello" value="World"/>
    </util:map>
  • list 列表

    1
    2
    3
    4
    5
    6
    7
    <util:list id="list">
    <ref bean="car"/>
    <bean class="com.yong.bean.Car">
    <property name="name" value="AUDI"/>
    <property name="price" value="122.3"></property>
    </bean>
    </util:list>
  • set 集合

    1
    2
    3
    <util:set id="set">
    <ref bean="car"/>
    </util:set>
  • properties 字符串键值对

    1
    2
    3
    <util:properties id="properties">
    <prop key="Hello">Spring</prop>
    </util:properties>

静态工厂bean

用于生产对象

由静态方法生产对象
默认为单例只生产一个对象 在容器启动时生产

1
<bean id="staticCarFactory" class="com.yong.bean.CarFactory" factory-method="getCar"/>

实例工厂bean

作为生产其他bean的工厂

1
2
3
4
5
6
7
<bean id="instanceCarFactory"  class="com.yong.bean.CarFactory"/>
<!-- 使用工厂bean生产对象-->
<bean id="car" lazy-init="false" class="com.yong.bean.Car" factory-bean="instanceCarFactory" factory-method="instanceCarFactory" facroty-method="getCar"/>
<!--
通过实现FactoryBean 接口的工厂bean只有在 只有getBean时使用时才会生产对象
-->
<bean id="factoryImplemention" lazy-init="false" class="com.yong.bean.CarFactory"/>

引用外部资源

1
2
3
4
5
6
<!-- 
引入外部资源
通过${}获取资源键值
${username} 为 本机用户名
-->
<context:property-placeholder location="classpath:bean.properties"/>

注解扫描

通过扫描指定包下的到注解的bean类并将其加入IOC容器

1
2
3
4
5
6
7
8
9
10
11
12
<context:component-scan base-package="com.yong.bean" use-default-filters="true">
<!--
排除某些类
assignable 指定类全路径
annotation 指定注解全路径
<context:exclude-filter type="assignable" expression="com.yong.bean.Person"/>
-->
<!--
指定哪些类 需要 use-default-filters="false"
<context:include-filter type="assignable" expression="com.yong.bean.Car"/>
-->
</context:component-scan>

DI 依赖注入

IOC 容器可根据bean.xml配置 在创建bean实例时注入依赖的bean

constructor-arg

通过构造器注入

1
2
3
4
5
6
7
8
<bean name="password" class="java.lang.String">
<!-- 直接注入值 -->
<constructor-arg value="admin123"/>
</bean>
<bean name="username" class="java.lang.String">
<!-- 引用其他bean -->
<constructor-arg ref="password"/>
</bean>

property

通过setter 注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean name="address" class="com.yong.domain.Address">
<property name="address" value="Hunan"/>
</bean>
<!-- 简化版 -->
<bean name="user" class="com.yong.domain.User" p:username="username" p:password="admin23" lazy-init="true"/>

<!-- 嵌套property -->
<bean name="addr" class="com.yong.domain.Address">
<property name="address">
<!-- 嵌套内部bean -->
<bean class="java.lang.String">
<constructor-arg value="湖南"/>
</bean>
</property>
</bean>

autowire

自动寻找其他bean并注入 只能注入引用类型

1
2
3
4
5
6
7
<!--
autowire: byName 通过名称 byType 通过类型(当同一类型的bean超过1个时 需要在希望注入的bean上使用primary="true")
-->
<bean name="user4" class="com.yong.domain.User" autowire="byName" lazy-init="true" scope="singleton">
<property name="username" ref="username"/>
<property name="password" ref="password"/>
</bean>

注解

  • @Resource

    javax提供的注解

    可通过指定名称和类型注入(默认通过名称)

    强制要求必须注入成功 注入失败则抛出异常

  • @Autowired

    spring提供的自动注入注解 用以代替@Inject

    可通过required指定是否必须

  • @Inject

    JSR-330规范

  • @Qualifier

    通过名称在多个候选bean之间确定符合条件的bean

    Autowired+Qualifier=Resource

AOP

面向切面编程

对一些代码(在java中就是方法)的所有同一执行点 进行抽取 形成一个切面 并专注于在切面上完成各种处理逻辑 就称为面向切面编程 AOP的价值在于抽取重复代码 并通过代理模式将切面 无侵入地植入目标执行点 使目标专注于自己的业务逻辑

spring aop 借鉴了aspectj的语法风格 并通过jdk动态代理和cglib两种方式实现代理

IOC容器通过AOP配置信息生成代理类

当目标类实现了接口时 使用JDK动态代理 否则将使用CGLIB 可通过

相关概念 详见官方文档

  • Aspect 切面类 一系列横切关注点(方法执行点)的集合
  • Join point 连接点 程序的执行点
  • Advice 通知 切面在特定连接点执行的动作
  • Pointcut 切点 连接点的集合
  • Target object 目标对象
  • Introduction 引入 表示目标对象实现的接口
  • Weaving 织入 通知与连接点结合形成代理类的过程

基本配置

XML

使用bean.xml配置文件的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- xml配置aop-->
<aop:config>
<!-- 定义一个切入点表达式: 拦截哪些类的哪些方法 多个用引号或逗号隔开-->
<aop:pointcut expression="execution(* com.yong.dao.UserDao.*(..))" id="pt"/>
<!-- 切面 -->
<aop:aspect ref="aop">
<!-- 环绕目标执行 -->
<aop:around method="round" pointcut-ref="pt"/>
<!-- 前置通知: 在目标方法调用前执行 -->
<aop:before method="before" pointcut-ref="pt"/>
<!-- 后置通知:目标方法调用后执行 -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 方法返回后执行 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 异常通知 -->
<aop:after-throwing method="throwError" pointcut-ref="pt"/>
<!--
around的顺序会影响around before的执行顺序
-->
</aop:aspect>
</aop:config>

注解

支持通过注解方式配置AOP

需要在bean.xml中声明

<aop:aspectj-autoproxy/> 或基于注解的配置类中使用@EnableAspectJAutoProxy 开启AspectJ风格的aop 可通过设置proxy-target-classtrue强制使用CGLIB代理

1
2
3
4
5
6
@Component
@Aspect // 标记当前类为切面类
public class Aop {
// 切点
@Pointcut("execution(* com.yong.dao.UserDao.*(..))")
public void pointCut(){ }

通知类型

spring aop 支持多种通知类型 在不同的连接点执行不同的逻辑

通知执行顺序

Around before -> before -> Around returning / throwing -> Around After -> afterReturning -> after

当多个切面类对同一目标进行增强时 会相互影响 可通过@Order注解 改变切面执行顺序

切面1 -> 切面2 -> 目标 -> 切面2 -> 切面1

  • Around 环绕通知 优先于其他通知执行 可替代其他通知 会影响当前切面类的其他通知

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Around("doARround()")
    public Object round(ProceedingJoinPoint pjp) {
    String methodName = pjp.getSignature().getName(); // 获取方法名
    Object o = null;
    try {
    // 调用目标方法
    System.out.println("[dBUtils2][around] before..."); // Before
    o = pjp.proceed(pjp.getArgs()); /
    System.out.println("[dBUtils2][around] after returning: "+o); // After
    // throw new RuntimeException();
    } catch (Throwable e) {
    System.out.println("[dBUtils2][around] after throwing..."); // After Throwing
    // e.printStackTrace();
    throw new RuntimeException(e); // 抛出异常通知其它通知 如果不抛其他通知将无法感知
    }finally {
    System.out.println("[dBUtils2][around] after..."); // After
    }
    return o; // 会影响当前切面类的其他AfterReturning通知获取的返回结果
    }
  • Before 前置通知

  • After 后置通知

  • AfterThrowing 抛出异常后通知

  • AfterReturning 返回后通知

切点表达式

spring aop 支持多种切点表达式

可通过各种条件 决定那些类应该被代理

  • * 通配符 匹配所有 多个通配符可以合成一个
  • .. 匹配任意参数
  • 支持逻辑运算符 && || ! 混合多个切点表达式

举个栗子

1
2
3
// 定义一个切点 切点关键字(访问修饰符 返回类型 类全限定名.方法名(参数))
@Pointcut("target(com.yong.springbootaop.service.CalculatorImpl)* com.yong.springbootaop.service.CalculatorImpl.*())")
public void pointCut(){ }
  • execution 匹配方法执行的连接点
  • within 限制匹配的连接点为为指定类型
  • this 限制AOP代理类为指定的类型
  • target 限制目标类为指定的类型
  • args 限制连接点的参数为指定类型
  • @target 限制目标拥有指定的注解类型
  • @args 限制连接点参数具有指定的注解类型
  • @annotation 限制连接点(方法)具有指定的注解类型
  • bean 具有指定名称的bean

事务管理

spring的事务管理是AOP的应用典范

声明式事务

可通过xml或注解方式声明事务

  • xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!--事务管理器 -->
    <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:advice id="td" transaction-manager="tm">
    <tx:attributes >
    <!-- 配置事务增强 -->
    <tx:method name="*" propagation="REQUIRED" />
    </tx:attributes>
    </tx:advice>
    <aop:config>
    <aop:pointcut expression="execution(* com.yong.jdbc.service.*.*(..))" id="pointCut"/>
    <!-- 指定事务增强-->
    <aop:advisor advice-ref="td" pointcut-ref="pointCut"/>
    </aop:config>
  • 注解

    需要在bean.xml 中配置 <tx:annotation-driven transaction-manager="tm"/>注解驱动事务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * <h4>声明式事务</h4>
    * isolation 事务隔离级别<br>
    * noRollbackFor 指定某些异常不会滚<br>
    * RollbackFor 指定哪些异常回滚<br>
    * timeout 设置超时时间<br>
    * readOnly 是否只读事务(效率更高)<br>
    * @TIP <strong>在同一个类中 所有事务方法的嵌套都会被当成一个事务</strong>
    * @param fromId
    * @param toIdisolation
    * @param cash
    */
    @Transactional
    public void transfer(int fromId, int toId, float cash) {
    // 自动提交事务时 一句sql就是一个事务
    String sql = "update t_account set balance = balance - ? where id = ?";
    jdbcTemplate.update(sql, cash, fromId);
    // int i = 1 / 0;
    String sql1 = "update t_account set balance = balance + ? where id = ?";
    jdbcTemplate.update(sql1, cash, toId);
    }

事务的传播行为

可通过propagation指定事务的传播行为

  • REQUIRED 绑定到一个已存在的事务中 如果不存在则创建一个新事务
  • REQUIRES_NEW 总是开启一个新事务 并挂起当前事务
  • SUPPORTS 支持在事务中运行 不存在事务也可运行
  • NOT_SUPPORTED 非事务执行 将当前事务挂起
  • MANDATORY 需要在事务内运行 如果不存在事务 则抛出异常
  • NEVER 永不在事务内执行 如果存在事务则抛出异常
  • NESTED 如果当前存在事务 则在嵌套事务内执行 如果当前没有事务 则新建一个事务

嵌套事务

在同一个类中 所有事务方法的嵌套都会被当成一个事务

嵌套事务执行过程:

事务A -> 事务A挂起 开启新的事务B(REQUIRES_NEW) -> 事务A继续执行