【框架】Spring

127 阅读10分钟

1. 概述

  • Spring是轻量级的开源的Java EE框架,可以解决企业应用开发的复杂性

  • 两个核心:IOC和AOP

    IOC:控制反转,把创建对象和对象调用的过程交给Spring管理

    AOP:面向切面,不修改源代码的情况下进行功能增强

  • 它有以下特点

    1、方便解耦,简便开发

    2、支持AOP

    3、方便程序的测试

    4、方便集成各种其他框架

    5、方便进行实务操作

    6、降低Java EE API使用难度

2. IOC

2.1 原理

1、在xml中配置相应的bean

2、解析xml得到bean的类型

3、通过反射创建对象

  Class clazz = Class.forName("...");

  return clazz.newInstance();

4、IOC思想是基于其容器完成的,容器底层本质上就是对象工厂。Spring提供了IOC容器的两种实现方式:

  • BeanFactory:是Spring内部的使用接口,一般不用于开发

    加载配置文件时不会创建对象,在获取对象时才创建对象

  • ApplicationContext:是前者的子接口,功能更多,一般使用该接口开发

    1、加载配置文件时就会创建对象

    2、两个常用实现类FileSystemXmlApplicationContext和ClassPathXmlApplicationContext

2.2 Bean管理操作

  Bean管理就是由Spring创建对象并且由Spring为对象注入属性。

2.2.1 Bean的作用域

  Bean的作用域有两种:单例和多例,默认是单例。

(1)在Spring配置文件中,通过<bean>标签的scope属于可以设置是单例(singleton)还是多例(prototype)

(2)当是多例时,不是在加载配置文件时创建对象,而是调用getBean()获取时才创建

2.2.2 Bean的生命周期

(1)通过构造器创建bean实例

(2)为bean的属性赋值

(3)调用postProcessBeforeInitialization()方法(如果配置了后置处理器:自定义BeanPostProcessor实现类并重写其方法,在配置文件里声明这个自定义类即可)

(4)调用bean的初始化方法,在配置文件中使用init-method属性配置

(5)调用postProcessAfterInitialization()方法(如果配置了后置处理器)

(6)bean被使用

(7)当容器关闭时,调用bean的销毁方法,在配置文件中使用destroy-method属性配置

2.2.2 基于xml文件

1、基于xml文件创建对象

(1)在Spring配置文件中使用<bean>标签

(2)<bean>标签中有很多属性

(3)创建对象时,默认执行无参构造方法

2、基于xml文件注入属性

(1)DI:依赖注入

(2)使用set方法注入

<bean id="user1" class="pers.ljc.springtest.bean.User">
    <property name="age" value="10"/>
    <property name="name" value="张三"/>
</bean>

(3)使用有参构造方法注入

<bean id="user1" class="pers.ljc.springtest.bean.User">
    <constructor-arg name="age" value="10"/>
    <constructor-arg name="name" value="张三"/>
</bean>

2.2.2 基于注解

(1)创建对象,常用注解如下所示

  • @Component(value = "..."),不写value的时候,默认是首字母小写的类名
  • @Controller
  • @Service
  • @Repository

(2)属性注入

  • @Autowired:根据属性类型自动注入

  • @Qualifier:根据名称注入,要和@Autowired一起用

  • @Resource:即可根据类型注入,也可以根据名称注入。注意该注解是javax包里的

    @Resource:什么都不写,根据类型注入

    @Resource(name = "..."):根据名称注入

  • @Value:注入普通类型属性

3、完全注解开发

(1)创建配置类

@Configuration
// 组件扫描
@ComponentScan(basePackages = {"pers.ljc.springtest"})
// 开启代理
@EnableAspectJAutoProxy
public class MyConfig {
}

(2)使用AnnotationConfigApplicationContext容器来获取bean

@Test
public void test4() {
    ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
    UserDao userDao = context.getBean("userDaoImpl", UserDao.class);
    userDao.add();
}

3. AOP

3.1 概念

  面向切面编程:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑的各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。在不修改源代码的情况下进行功能增强。

  • 连接点:在一个类中,哪些方法可以被增强,这些方法就是连接点

  • 切入点:实际被增强的方法

    execution([权限修饰符]  [返回类型]  [全路径类]  [方法名称]  [参数列表])
    
    例子:
    // 增强User的say()方法,返回类型可省略
    execution(* pers.ljc.springtest.bean.User.say(..))
    // 增强User的所有方法
    execution(* pers.ljc.springtest.bean.User.*(..))
     // 包里面的所有类的所有方法
    execution(* pers.ljc.springtest.bean.\*.\*(..))
    
  • 通知(增强):实际增强的逻辑部分,如为某个方法增加记录日志,这里的记录日志就是通知。有五种

    1、@Before:切入点之前执行

    2、@AfterReturning:切入点之后执行,抛异常则不执行

    3、@Around:切入点前后都执行

    4、@AfterThrowing:切入点抛出异常后执行

    5、@After:无论有没有抛异常,都执行

  • 切面:指的是一个动作,把通知应用到切入点的过程

3.2 原理

  AOP是通过动态代理实现的,动态代理有两种

(1)有接口的情况,使用JDK动态代理

(2)没有接口的情况,使用CGLIB动态代理

3.3 案例

  Spring一般都是基于AspectJ(这是单独的框架,不是Spring的组成部分)实现AOP操作。

1、创建需要被增强的类

@Component
public class UserDao {
    public void add() {
        System.out.println("保存用户...");
    }
}

2、创建增强类

@Component
@Aspect
public class UserDaoProxy {
    // 前置通知
    @Before(value = "execution(* pers.ljc.springtest.dao.UserDao.add(..))")
    public void before() {
        System.out.println("before");
    }
}

3、进行通知的配置

(1)在Spring的配置文件中,开启注解扫描

(2)使用注解创建UserDao和UserDaoProxy

(3)在增强类上添加@Aspect注解

(4)在Spring配置文件中开启生成代理对象

<!--开启注解扫描-->
<context:component-scan base-package="pers.ljc.springtest" />
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy />

4、配置不同类型的通知

5、测试方法

@Test
public void test2() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    UserDao userDao = context.getBean("userDao", UserDao.class);
    userDao.add();
}

五种类型的顺序如下图所示: 028-增强顺序.png

6、可以先定义一个切入点,然后再引用

@Pointcut(value = "execution(* pers.ljc.springtest.dao.UserDao.add(..))")
public void point() {}

@Before(value = "point()")
public void before() {
    System.out.println("before");
}

7、当有多个增强类对同一个方法进行增强时,可在增强类上使用@Order(数值),数值越小优先级越高

4. 事务

  事务是数据库操作的最基本单元,是一组操作,要么都成功,要么都成功。如果某个操作失败,则全部失败。

  事务的特性(ACID):

  • 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态
  • 一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定
  • 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
  • 持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的

4.1 Spring的事务API

  Spring提供了一个PlatformTransactionManager接口,代表事务管理器,这个接口针对不同的框架提供了不同的实现类。

(1)在配置文件中配置事务管理器

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="driverClass" value="${jdbc.driverClass}"/>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
    <property name="initialPoolSize" value="${initPoolSize}"/>
    <property name="maxPoolSize" value="${maxPoolSize}"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

(2)在配置文件中开启事务注解

<tx:annotation-driven transaction-manager="transactionManager"/>

(3)在service层的类或方法上加上@Transactional注解

(4)@Transactional注解的属性

  • propagation:传播行为
  • isolation:隔离级别
  • timeout:超时时间
  • readOnly:是否只读
  • rollbackFor:回滚
  • noRollbackFor:不回滚

4.2 事务的传播行为

  methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

4.2.1 PROPAGATION_REQUIRED

  表示当前方法必须运行在事务中。如果当前事务存在,方法就会在该事务中运行。否则,会启动一个新事务。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB();
    // do something
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // do something
}
  • 单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
  • 调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。

4.2.2 PROPAGATION_SUPPORTS

  表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB();
    // do something
}

// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // do something
}
  • 单独调用methodB时,methodB方法是非事务地执行的。
  • 当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

4.2.3 PROPAGATION_MANDATORY

  表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB();
    // do something
}

// 事务属性为MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
    // do something
}
  • 单独调用methodB时,因为当前没有一个活动的事务,则会抛出IllegalTransactionStateException异常。
  • 当调用methodA时,methodB则加入到methodA的事务中,事务地执行。

4.2.4 PROPAGATION_REQUIRES_NEW

  表示当前方法必须运行在它自己的事务中。会新建事务,如果当前存在事务,则把当前事务挂起。使用JTATransactionManager作为事务管理器。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    doSomeThingA();
    methodB();
    doSomeThingB();
    // do something else
}


// 事务属性为REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // do something
}

则调用methodA方法相当于调用以下方法

public void methodA() {
	TransactionManager tm = null;
    try{
        tm = getTransactionManager();                   // 获得一个JTA事务管理器
        tm.begin();                                     // 开启一个新的事务
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();					// 挂起当前事务
        try{
            tm.begin();					// 重新开启第二个事务
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();				// 提交第二个事务
        } Catch(RunTimeException ex) {
            ts2.rollback();				// 回滚第二个事务
        } finally {
            // 释放资源
        }
        tm.resume(ts1);					// methodB执行完后,恢复第一个事务
        doSomeThingB();
        ts1.commit();					// 提交第一个事务
    } catch(RunTimeException ex) {
        ts1.rollback();					// 回滚第一个事务
    } finally {
        // 释放资源
    }
}

在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果仍然被提交。而除了methodB之外的其它代码导致的结果都会被回滚。

4.2.5 PROPAGATION_NOT_SUPPORTED

  表示该方法不应该运行在事务中。如果存在当前事务,则把该事务挂起。

4.2.6 PROPAGATION_NEVER

  表示该方法不应该运行在事务中。如果存在当前事务,则会抛出异常。

4.2.7 PROPAGATION_NESTED

  • 如果一个活动的事务存在,则运行在一个嵌套的事务中。
  • 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED属性执行。
  • 这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。
  • 需要JDBC 驱动的java.sql.Savepoint类。
  • 需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
  doSomeThingA();
  methodB();
  doSomeThingB();
}

@Transactional(propagation = Propagation.NEWSTED)
public void methodB() {
    ……
}

如果单独调用methodB()方法,则按TransactionDefinition.PROPAGATION_REQUIRED属性执行。如果调用methodA()方法,相当于以下效果

public void methodA() {
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try {
            methodB();
        } catch(RuntimeException ex) {
            con.rollback(savepoint);
        } finally {
            // 释放资源
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        // 释放资源
    }
}

  当methodB()方法调用之前,调用setSavepoint()方法,保存当前的状态到。如果methodB()方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码如doSomeThingB()方法调用失败,则回滚包括methodB()方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作;而内层事务操作失败并不会引起外层事务的回滚

4.2.8 NESTED与REQUIRES_NEW区别

  • 使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
  • PROPAGATION_REQUIRES_NEW启动一个新的,不依赖于环境的 “内部” 事务。这个事务将被提交或回滚而不依赖于外部事务,它拥有自己的隔离范围,自己的锁,等等。当内部事务开始执行时,外部事务将被挂起,内务事务结束时,外部事务将继续执行。
  • PROPAGATION_REQUIRES_NEWPROPAGATION_NESTED的最大区别在于,前者完全是一个新的事务,而后者则是外部事务的子事务,如果外部事务提交,嵌套事务也会被提交,这个规则同样适用于回滚。

5. 参考资料