温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Spring 入门

发布时间:2020-06-14 09:53:18 来源:网络 阅读:458 作者:灰白世界 栏目:编程语言

Spring是什么?

Spring是一个开源框架,为简化企业级应用开发而生。

Spring是一个 IOC 和 AOP 容器框架。

Spring 是轻量级的、面向切面编程、依赖注入的一站式框架。

搭建Spring开发环境

导入

commons-logging-1.1.1.jar

spring-beans-4.0.0.RELEASE.jar

spring-context-4.0.0.RELEASE.jar

spring-core-4.0.0.RELEASE.jar

spring-expression-4.0.0.RELEASE.jar

Bean配置

IOC & DI 概述

IOC:反转控制器,思想是反转资源获取的方向,传统资源查找方式是组件向容器发起请求查找资源,IOC 则是容器主动的将资源推送给它所管理的组件。

DI:依赖注入,组件预定义的方式接受资源注入。

Spring 容器

在 Spring IOC 容器读取 Bean 配置创建 Bean 实例之前,必须要对其进行实例化,只有实例化后,才可以从 IOC 容器中取出并使用。

Spring 提供了两种类型的 IOC:

BeanFactory:IOC 容器的基本实现。

ApplicationContext:提供了更多的特性,是 BeanFactory 的子接口,在初始化上下文阶段就实例化了所有单例Bean。

AppliactionContext 主要实现类

ClassPathXmlApplicationContext:从类路径下加载配置文件。

FileSystemXmlApplicationContext:从文件系统下加载配置文件。

ConfigurableApplicationContext:ApplicationContext 的扩展类,新增 refresh 和 close 方法,使其具有启动、刷新和关闭上下文的功能。

WebApplicationContext:Web 应用专属。

配置形式

基于 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="car" class="com.kernel.bean.Car"/>
</beans>

基于注解的方式

Spring 能从 classpath 中自动扫描、侦测和实例化具有特定注解的组件,包括:

@Component: 基本注解,标识一个受 Spring 管理的组件。

@Respository:标识持久层组件。

@Service:标识业务层组件。

@Controller:标识表现层组件。

对于扫描到的组件,Spring 有特定的命名策略,当然可以在注解中通过 value 属性指定组件名。

当在组件中使用了注解后,还需要在配置文件中声明\<context:component-scan>:

base-package:扫描组件基类包,Spring 容器将扫描该包下的所有类,当需要扫描多个包时,用逗号隔开。

resource-pattern:仅扫描基类中的特定类。

use-default-filters:默认为ture,扫描@Component、@Respository、@Service、@Controller 组件。

\<context:exclude-filter>:子节点表示要排除的的目标类。

\<context:include-filter>:子节点标识要包含的目标类,需要的 use-default-filters 配合使用。

类别 说明
annotation com.kernel.XxxAnnotation 所有标注了XxxAnnotation的类,该类型采用目标类是否标注了某个注解进行过滤
assignable com.kernel.XxxService 所有继承或扩展XxxService的类,该类型采用了目标类是否继承或扩展某个特定类进行过滤
aspectj com.kernel.*Service 所有类名义Service结束的类及继承或扩展它们的类,该类型采用AspectJ表达式进行过滤
regex com.kernel.anno.* 所有com.kernel.anno包下的类。该类型采用正则表达式,根据类的类名进行过滤
custom com.kernel.XxxTypeFilter 采用XxxTypeFilter通过代码的方式定义过滤原则。该类必须实现org.springframework.core.type.TypeFilter接口

Spring 还可以进行组件装配

\<context:component-scan> 元素还会自动注册 AutowiredAnnotationBeanPostProcessor 实例,该实例可以自动装配具有 @Autowired、@Resource 和 @Inject 注解的属性。

构造器、普通字段、具有参数的方法都可以应用 @Autowired。

默认,所有被设置为该注解的属性都要被设置,否则会抛出异常,可以使用该注解的 required 属性设为false。

当 IOC 中存在多个类型兼容的 Bean 时,自动装配失败,此时可以在 @Qualifier 注解中提供 Bean 的名称。

@Authwired 注解应用在数组类型的属性上,此时 Spring 将会把所有匹配的 Bean 进行自动装配。

@Authwired 注解应用在集合属性上, 此时 Spring 读取该集合的类型信息,然后自动装配所有与之兼容的 Bean。

@Authwired 注解用在 Map 上时, 若该 Map 的键值为 String,那么 Spring 将自动装配与之 Map 值类型兼容的 Bean,此时 Bean 的名称作为键值。

配置方式

通过全类名

<bean id="car" class="com.kernel.bean.Car"/>

通过工厂方法

静态工厂

调用静态工厂创建 Bean 是将对象创建的过程封装到静态方法中,当调用者需要时,只需调用静态方法就可以返回对象。

要声明静态工厂创建 Bean,在 class 属性中指定静态工厂,同时在 \<factory-method> 元素中指定可以返回对象的静态方法,最后使用 \<constructor-arg> 元素为该方法传递参数。

<bean id="car" class="com.kernel.bean.factory.StaticCarFactory" factory-method="getCar">
    <constructor-arg name="name" value="奥迪"/>
</bean>

实例工厂

调用实例工厂创建 Bean 是将对象封装到另一个对象的实例中,当调用者需要时,只需调用实例方法就可以返回对象。

要声明实例工厂创建 Bean,首先要声明一个实例工厂 Bean,然后在 \<factory-bean> 元素中指定实例工厂 Bean,在\<factory-method> 元素中指定工厂提供的方法,最后使用 \<constructor-arg> 元素为该方法传递参数。

<bean id="carFactory" class="com.kernel.bean.factory.InstanceCarFactory"/>
<bean id="car" factory-bean="carFactory" factory-method="getCar">
    <constructor-arg name="name" value="奥迪"/>
</bean>

Bean Factory

String 有两种 Bean,一种是普通的 Bean,另一种是 Bean Factory,它与普通的 Bean 不同,它返回的类型是是 getObject方法所返回的对象类型。

<bean id="car" class="com.kernel.bean.CarFactoryBean"/>

依赖注入

属性注入

<?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="car" class="com.kernel.bean.Car">
        <!--属性注入必须提供属性所对应的的set方法-->
        <!--属性注入使用<property>元素,使用name属性指定Bean的属性名称,value属性或<value>子节点指定属性值-->
        <!--若value属性中包含特殊字符,可以使用<![CDATA[]]把属性值包裹起来-->
        <property name="brand" value="奥迪"/>
        <property name="price" value="300000"/>
        <property name="speed">
            <value>200</value>
        </property>
    </bean>
</beans>

构造器注入

<bean id="user" class="com.kernel.bean.User">
    <!--使用构造器注入必须提供与之对应的构造函数-->
    <!--按索引匹配入参-->
    <constructor-arg name="username" index="0" value="kernel"/>
    <!--按索引匹配入参-->
    <constructor-arg name="password" index="1"value="123456"/>
    <!--按类型匹配入参-->
    <constructor-arg name="realName" type="java.long.String"value="灰白色"/>
    <!--引用注入-->
    <property name="car" ref="car"/>
</bean>

内部 Bean

当 Bean 实例仅仅给一个特定的属性使用时,可以将其声明为内部 Bean,内部 Bean 声明在<property>或者<value>元素中,不需要设置 id 和 name,不允许在其他地方使用。

<bean id="user" class="com.kernel.bean.User">
    <constructor-arg name="username" value="kernel"/>
    <constructor-arg name="password" value="123456"/>
    <constructor-arg name="realName" value="灰白色"/>
    <property name="car">
        <bean class="com.kernel.bean.Car">
            <property name="brand" value="奔驰"/>
            <property name="price" value="400000"/>
            <property name="speed" value="180"/>
        </bean>
    </property>
</bean>

集合属性

java.util.List 通过 \<list> 定义,标签里可以使用 \<value> 指定简单的常量值,使用 \<ref> 指定 Bean。

java.util.Set通过 \<Set> 定义,规则和 \<list> 一样。

java.util.Map 通过 \<map> 定义,标签里可以使用多个 \<entry> 作为子标签,每个标签包含一个 \<key> 和 \<value>。

java.util.Properties 通过 \<props> 定义,标签里使用多个 \<prop> 作为字标签,每个标签包含一个 \<key> 和\<value>。

<bean id="user" class="com.kernel.bean.User">
    <property name="username" value="kernel"/>
    <property name="password" value="123456"/>
    <property name="realName" value="灰白色"/>
    <property name="cars">
        <list>
            <ref bean="car"/>
            <ref bean="car"/>
        </list>
    </property>
    <property name="childs">
        <set>
            <ref bean="car"/>
        </set>
    </property>
    <property name="scores">
        <map>
            <entry key="语文" value="100"></entry>
            <entry key="数学" value="99"/>
        </map>
    </property>
    <property name="properties">
        <props>
            <prop key="url">jdbc:mysql:///test</prop>
        </props>
    </property>
</bean>

使用 utility scheme 定义集合

使用基本的集合标签不能将集合作为单独的 Bean 定义,导致其他的 Bean 无法引入,可以使用 util schema 的集合标签定义独立的集合 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"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       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-4.0.xsd">
    <bean id="car" class="com.kernel.bean.Car">
        <property name="brand" value="奥迪"/>
        <property name="price" value="300000"/>
        <property name="speed" value="180"/>
    </bean>
    <util:list id="cars">
        <ref bean="car"/>
        <ref bean="car"/>
        <ref bean="car"/>
    </util:list>
    <bean id="user" class="com.kernel.bean.User">
        <property name="username" value="kernel"/>
        <property name="password" value="123456"/>
        <property name="realName" value="灰白色"/>
        <property name="cars" ref="cars"/>
        <property name="childs">
            <set>
                <ref bean="car"/>
            </set>
        </property>
        <property name="scores">
            <map>
                <entry key="语文" value="100"></entry>
                <entry key="数学" value="99"/>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="url">jdbc:mysql:///test</prop>
            </props>
        </property>
    </bean>
</bean

p 命名空间

为了简化 XML 的配置,Spring2.5 开始引入了一个新的 p 命名空间,可以通过<bean>元素属性的方式配置属性。

<bean id="car" class="com.kernel.bean.Car" p:brand="奥迪" p:price="300000" p:speed="180"/>

自动装配

可以使用<bean>的 autowire 属性设置自动装配,有两种方式:

byName:根据名称装配,属性名和要装配 Bean 名称必须完全相同,否则装配失败。

byType:根据类型装配,如果存在两个类型相同的 Bean,则装配失败。

<bean id="address" class="com.kernel.bean.Address" p:city="上海" p:street="广川"/>
<bean id="car" class="com.kernel.bean.Car" p:brand="奥迪" p:price="300000" p:speed="180"/>
<bean id="person" class="com.kernel.bean.Person" autowire="byName"/>

缺点:对于一个 Bean,不能选择只装配其中的某一个属性,不够灵活,再一个是要么选择 byName,要么选择 byType,两者不能混搭。

Bean 之间的关系

继承关系

Spring 允许继承 Bean 的配置,子 Bean 从父 Bean 继承配置,如果父 Bean 只想作为模板,可以将父 bean 设为 abstract,并不是所有的 Bean 配置都会被继承,如果父 Bean 的 class 属性没有配置,它必须为抽象 Bean。

<bean id="address" class="com.kernel.bean.Address" p:city="上海" p:street="广川" abstract="true"/>
<bean id="address1" parent="address"/>

依赖关系

Soring 允许设置前置依赖,可以通过 depends-on 属性设置依赖 Bean,多个 Bean 通过逗号或空格隔开,依赖 Bean 在本 Bean 实例化之间实例化。

<bean id="person" class="com.kernel.bean.Person" depends-on="car address1" p:car-ref="car" p:address-ref="address1"/>

Bean 的作用域

在 Spring 中,允许在<bean>元素的 scope 属性设置 Bean 的作用域。

singleton:在初始化 IOC 容器的时候就会创建一个实例,再次获取,都会返回这一个实例。

prototype:在初始化 IOC 容器的时候不会创建实例,每次获取都会返回一个不同的实例。

request:每次请求 HTTP 都会返回一个新的 Bean 实例,仅适用于 WebApplicationContext。

seesion:同一个 HTTP Session 使用一个 Bean 实例,仅适用于WebApplicationContext。

使用外部属性文件

配置 Bean 时,有时候需要在配置文件中混入系统部署的细节信息,而这些部署文件要和 Bean 配置文件分离。Spring 提供了一个 PropertyPlaceholderConfigurer 的 BeanFactory 后置处理器,这个处理器允许将部署信息存放到外置文件中,可以在 Bean 配置中使用 ${var} 的方式引入属性值。

<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${user}"/>
    <property name="password" value="${password}"/>
    <property name="driverClass" value="${driverClass}"/>
    <property name="jdbcUrl" value="${jdbcUrl}"/>
</bean>

SpEL

Spring 表达式语言,支持运行时查询和操作对象的表达式语言。

以#{}作为定界符。

支持算数、比较、逻辑运算符,支持字符串拼接和 if、else。

通过 SpEL 可以实现:

引用 bean

调用方法以及引入对象的属性

计算表达式的值

正则表达式的匹配

<bean id="car" class="com.kernel.bean.Car">
    <property name="brand" value="奥迪"/>
    <property name="price" value="#{T(Integer).MAX_VALUE}"/>
    <property name="speed" value="#{T(Integer).MIN_VALUE}"/>
</bean>
<bean id="address" class="com.kernel.bean.Address">
    <property name="city" value="#{car.price > 300000?'上海':'高唐'}"/>
</bean>
<bean id="person" class="com.kernel.bean.Person">
    <property name="name" value="#{car.brand}"/>
    <property name="car" value="#{car}"/>
    <property name="address" value="#{address}"/>
</bean>

Bean的生命周期

Spring IOC 可以管理 Bean 的生命周期并允许在特定点执行定制任务。

Spring ICO 对 Bean 的生命周期管理的过程:

构造 Bean 实例。

为 Bean 设置属性。

初始化 Bean。

当容器关闭时,调用 Bean 的销毁方法。

在 Bean 的生命中可以通过 init-method 和 destroy-method 指定初始化和销毁方法。

Bean 的后置处理器允许在调用初始化方法之前对 Bean 进行额外处理。

Bean 的后置处理器对所有 Bean 实例逐一处理,对于 Bean 后置处理器来讲,必须实现 BeanPostProcessor,

Spring 将每个 Bean 传递给两个方法:

Object postProcessBeforeInitialization:在初始化方法调用前执行。

Object postProcessAfterInitialization:在初始化方法调用后执行。

添加 Bean 后置处理器后 Bean 的生命周期

构建 Bean 实例。

为 Bean 设置属性。

将 Bean 传递给 Bean 后置处理器的 postProcessBeforeInitialization 方法。

初始化 Bean。

将 Bean 传递给 Bean 后置处理器的 postProcessAfterInitialization 方法。

当容器关闭时,调用 Bean 的销毁方法。

<bean id="car" class="com.kernel.bean.Car" p:brand="奥迪" p:price="300000" p:speed="200" init-method="init" destroy-method="destroy"/>
<!--配置Bean后置处理器-->
<bean class="com.kernel.MyBeanPostProcessor"/>

Spring 4.x 新特性:泛型依赖注入

Spring 入门

创建两个泛型类,并配置两者的依赖关系,对于继承这两个类的子类,如果泛型相同,则会继承这种依赖关系。

Spring AOP

提出需求

当程序在运行期间需要给程序的运行增加一些特定需求,例如日志,可以修改代码实现,但是要修改的函数过多,就会特别麻烦。可以使用动态代理实现:

使用一个代理将对象包裹起来,让代理对象代替原对象执行方法。

public class ArithmeticCalculatorProxy {
    // 被代理对象
    private ArithmeticCalculator target = null;

    public void ArithmeticCalculator(ArithmeticCalculator target) {
        this.target = target;
    }

    public ArithmeticCalculator getProxy(){
        // 代理对象
        ArithmeticCalculator proxy;
        // 代理对象用哪个类加载器加载
        ClassLoader classLoader = target.getClass().getClassLoader();
        // 代理对象的类型
        Class[] interfaces = new Class[]{ArithmeticCalculator.class};
        // 当代理对象执行其中的方法时的逻辑
        InvocationHandler h = new InvocationHandler() {
            /**
             * @param proxy 要返回的代理对象
             * @param method 正在被调用的方法
             * @param args 传参
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("The method" + method.getName() + "begins with" + Arrays.asList(args));
                Object result = method.invoke(args);
                return result;
            }
        };
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(classLoader, interfaces, h);
        return proxy;
    }
}

AOP 简介

面向切面编程(AOP)是对面向对象编程(OOP)的一个补充,AOP 的主要关注对象是切面(aspect)。

面向切面编程时,仍需定义公共功能,但可以明确定义功能在哪里,以什么方式应用,并且无需更改受影响的类。

AOP 的好处是:

每个事物逻辑位于一个文件,代码不分散,便于维护和升级。

业务模块更简洁,只包含核心业务代码。

AOP 术语:

切面:公共功能,在上例中切面就是日志功能。

通知:切面必须要完成的工作,上例中通知就是加减乘除。

目标:被通知的对象。

代理:代理类。

连接点:程序执行的某个特定位置。

切点:每个类拥有多个连接点,AOP 通过切点定位到特定的连接点。

AspectJ

Java 社区中最流行的 AOP 框架。

基于注解方式,要在 IOC 容器中添加注解支持,只需要在配置文件中添加<aop:aspectj-autoproxy/>,自动为匹配到类生成代理对象

要在 Spring 中声明切面,只需要将其声明为 Bean,当在 IOC 容器中声明切面类后,IOC 就会为那些与切面类匹配的类生成代理对象,切面是一个带有@Aspect 注解的 Java 类。

通知是标注有某种注解的 Java 方法。

Aspect 支持五种类型的通知注解:

@Before

/**
* 前置通知,方法执行前执行
*/
@Before("execution(* com.kernel.spring.aop.impl.ArithmeticCalculatorImpl.*(..) )")
public void beforeMethod(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    System.out.println("The method:" + methodName + " begins with " + args);
}

@After

/**
* 后置通知,方法执行后执行
*/
@After("execution(* com.kernel.spring.aop.impl.ArithmeticCalculator.*(..))")
public void afterMethod(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("The method:" + methodName + " end");
}

@AfterReturning

/**
* 返回值通知,方法执行成功后执行,可以访问到方法的返回值
*/
@AfterReturning(value = "execution(* com.kernel.spring.aop.impl.ArithmeticCalculator.*(..))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("The method:" + methodName + " end with " + result);
}

@AfterThrowing

/**
* 异常通知,方法执行发生异常后执行,可以访问到异常对象,同时可以在出现特定异常后执行响应的逻辑
*/
@AfterThrowing(value = "execution(* com.kernel.spring.aop.impl.ArithmeticCalculator.*(..))", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("The method " + methodName + " occurs exception:" + ex);
}

@around

/**
* 环绕通知需要携带 proceedingJoinPoint 类型的参数
* 环绕通知相当于动态代理的全过程
* 环绕通知必须有返回值,返回值即为方法执行后的返回值
*/
@Around(value = "execution(* com.kernel.spring.aop.impl.ArithmeticCalculator.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) {
    Object result = null;
    String methodName = proceedingJoinPoint.getSignature().getName();
    try {
        // 前置通知
        System.out.println("The method:" + methodName + " begins with " + Arrays.asList(proceedingJoinPoint.getArgs()));
        result = proceedingJoinPoint.proceed();
        // 返回值通知
        System.out.println("The method:" + methodName + " end with " + result);
    } catch (Throwable e) {
        // 异常通知
        System.out.println("The method occurs exception:" + e);
    }
    // 后置通知
    System.out.println("The method:" + methodName + " end");
    return result;
}

优先级:可以通过 @order 注解设置切面类的优先级

基于 XML 方式

<bean id="arithmeticCalculator" class="com.kernel.spring.aop.impl.ArithmeticCalculatorImpl"/>
<bean id="loggerAscpect" class="com.kernel.spring.aop.impl.LoggerAscpect"/>
<!--配置AOP-->
<aop:config>
    <!--配置切入点表达式-->
    <aop:pointcut id="pointcut"
                  expression="execution(* com.kernel.spring.aop.impl.ArithmeticCalculatorImpl.*(..) ))"/>
    <!--配置切面及通知-->
    <aop:aspect ref="loggerAscpect" order="1">
        <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
        <aop:after method="afterMethod" pointcut-ref="pointcut"/>
        <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/>
        <aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/>
        <aop:around method="around" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

JdbcTemplates

为了使 JDBC 更加易用,Spring 在 JDBC API 上建立了一个抽象层,缺点是毕竟是 Spring 提供了一个小工具,不支持级联操作。

UPDATE

使用 SQL 语句更新数据库

String sql = "UPDATE empoyees SET EMAIL = ? WHERE id = ?";
int update = jdbcTemplate.update(sql, "Tom@163.com", 1);

批量更新

String sql = "INSERT INTO empoyees(LAST_NAME, EMAIL,DEPT_ID) VALUES (?, ?, ?)";
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[]{"AA","aa@163.com", 1});
batchArgs.add(new Object[]{"BB","bb@163.com", 2});
batchArgs.add(new Object[]{"CC","cc@163.com", 3});
batchArgs.add(new Object[]{"DD","dd@163.com", 2});
batchArgs.add(new Object[]{"EE","ee@163.com", 1});
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);

SELECT

查询单个记录

String sql = "SELECT ID, LAST_NAME lastName, EMAIL FROM empoyees where id = ?";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);

查询多个记录

String sql = "SELECT ID, LAST_NAME lastName, EMAIL FROM empoyees";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
List<Employee> maps = jdbcTemplate.query(sql, rowMapper);

Spring 事务

事务是企业级开发必不可少的技术,用来保证数据的一致性和完整性,事务是一系列的操作,要么都完成,要么都不完成。

事务的特性

原子性:事务中包含的逻辑不可再分。

一致性:事务执行前后,数据的完整性保持一致。

隔离性:事务在执行期间,不能受到其他事务的影响。

持久性:事务执行成功,应该持久化到磁盘上。

事务的传播行为

当事务方法被另一个事务调用时,必须指定事务的传播方式。

使用 @Transactional 注解中的 Propagation 指定事务的传播行为。

REQUIRED:如果有事务在运行,当前方法就在这个事务中运行,否则创建新事务。

REQUIRES_NEW:当前方法必须创建新事务,并在自己的事务中运行,原事务挂起。

事务的安全问题

脏读是指读到了别的事务回滚前的脏数据。

不可重复读取是指同一个事务在整个事务过程中对同一笔数据进行读取,每次读取结果都不同。

幻读是指同样一笔查询在整个事务过程中多次执行后,查询所得的结果集是不一样的。

事务的隔离级别

Read Uncommitted 读未提交 ,引发脏读问题

Read Committed 读已提交,解决脏读,引发不可重复读问题(Oracle默认隔离级别)。

Repeatable Read 重复读,解决不可重复读,未解决幻读(MySQL默认隔离级别)

Serializable,可串行化 解决所有问题

隔离级别分类

按性能从高到低可划分为:读未提交>读已提交>重复读>可串行化

按拦截程序从高到低可划分为:可串行化>重复读>读已提交>读未提交

配置事务

注解方式

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

<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    // 使用Propagation指定事务的传播行为
    // 使用Isolation指定事务的隔离级别
    // 使用noRollbackFor指定事务不回滚的异常
    // 使用readOnly指定事务是否只读
    // 使用timeout指定强制回滚操作之前事务占用的时间
    @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED, noRollbackFor = BookStockException.class)
    @Override
    public void purchase(String username, String isbn) throws Exception {
        int bookPriceByIsbn = bookShopDao.findBookPriceByIsbn(isbn);
        bookShopDao.updateBookStock(isbn);
        bookShopDao.updateUserAccount(username, bookPriceByIsbn);
    }
}

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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       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/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>

    <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>

        <property name="initialPoolSize" value="${jdbc.initalPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>

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

    <bean id="bookShopDao" class="com.kernel.spring.tx.xml.BookShopDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="bookShopService" class="com.kernel.spring.tx.xml.BookShopServiceImpl">
        <property name="bookShopDao" ref="bookShopDao"/>
    </bean>

    <bean id="cashier" class="com.kernel.spring.tx.xml.CashierImpl">
        <property name="bookShopService" ref="bookShopService"/>
    </bean>

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

    <!--配置事务属性-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="purchase" propagation="REQUIRES_NEW"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!--配置事务切入点-->
    <aop:config>
        <aop:pointcut id="txPointcut" expression="execution(* com.kernel.spring.tx.xml.BookShopService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
</beans>

Spring Web 应用

在 web.xml 中配置 Spring 配置文件的名称和路径,并且配置启动 IOC 容器的 ServletContextListener。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置Spring配置文件的名称和路径-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!--启动IOC容器的ServletContextListener-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

获取 IOC 容器:

在 Web 应用中,我们用到的 IOC 容器是 WebApplicationContext,它继承自 ApplicationContext,它的初始化方式和 ApplicationContext 有所不同,因为 WebApplicationContext 需要 ServletContext 实例,所以我们必须在 web.xml 配置 Servlet 或者 Web 容器监听器。

Spring 提供了用于启动 WebApplicationContext 的 Servlet 和 Web 容器监听器:

org.springframework.web.context.ContextLoaderServlet

org.springframework.web.context.ContextLoaderListener

那么 ContextLoaderListener 做了什么呢?

@Override
public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
}

继续追踪 WebApplicationContext,ContextLoader 将 WebApplicationContext 放到了 Servlet中,Servlert 是一个 MAP 容器,WebApplicationContext 的 KEY 是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。

Spring提供了一个WebApplicationContextUtils类,可以方便的取出WebApplicationContext,只要把ServletContext传入就可以了。

ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI