Spring:现代Java开发的必备框架

19 阅读16分钟

Spring:现代Java开发的必备框架

Spring是一个轻量级的Java框架,它提供了各种企业级应用程序开发的工具和技术。Spring框架的核心是IoC容器和AOP框架。IoC容器使得Java应用程序的组件化变得更加容易,AOP框架使得Java应用程序的切面编程变得更加容易。Spring框架还提供了许多其他的功能,例如数据访问、Web开发、安全性、事务管理等。

Spring创建bean的生命周期以及对应的接口和注解

Spring创建bean的生命周期包含以下步骤:

  1. 实例化Bean:Spring通过构造器或工厂方法来创建Bean实例。
  2. 设置Bean属性:Spring通过setter方法或直接访问字段来设置Bean的属性值。
  3. BeanNameAware接口:如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法。
  4. BeanFactoryAware接口:如果Bean实现了BeanFactoryAware接口,Spring将BeanFactory实例传递给setBeanFactory()方法。
  5. ApplicationContextAware接口:如果Bean实现了ApplicationContextAware接口,Spring将ApplicationContext实例传递给setApplicationContext()方法。
  6. Pre-Initialization BeanPostProcessor:在Bean初始化之前,Spring通过调用PostProcessBeforeInitialization()方法提供了一个扩展点,可以在Bean初始化之前对Bean进行定制。
  7. InitializingBean接口:如果Bean实现了InitializingBean接口,Spring将调用afterPropertiesSet()方法。
  8. 自定义初始化方法:Bean可以自定义初始化方法,只需要在Bean定义中指定该方法的名称。
  9. Post-Initialization BeanPostProcessor:在Bean初始化之后,Spring通过调用PostProcessAfterInitialization()方法提供了一个扩展点,可以在Bean初始化之后对Bean进行定制。
  10. DisposableBean接口:如果Bean实现了DisposableBean接口,Spring将调用destroy()方法。
  11. 自定义销毁方法:Bean可以自定义销毁方法,只需要在Bean定义中指定该方法的名称。

在Spring中,可以使用以下注解来控制Bean的生命周期:

  • @PostConstruct:指定初始化方法,相当于InitializingBean接口的afterPropertiesSet()方法。
  • @PreDestroy:指定销毁方法,相当于DisposableBean接口的destroy()方法。
  • @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod"):指定初始化方法和销毁方法。

Spring使用三级缓存解决循环依赖的原理

  1. singletonObjects:保存已经完全创建好的单例Bean。
  2. earlySingletonObjects:保存已经实例化、但是还未填充属性的单例Bean。
  3. singletonFactories:保存Bean的工厂方法。

当两个Bean相互依赖时,Spring会先使用工厂方法创建一个Bean的代理对象,然后将代理对象放入到singletonFactories缓存中。接着,Spring会继续创建被依赖的Bean。如果创建被依赖的Bean时,需要引用到代理对象,Spring会先到singletonFactories缓存中查找代理对象,如果找到了,就使用代理对象,否则就继续创建代理对象。当被依赖的Bean创建完成后,Spring会将被依赖的Bean放入到singletonObjects缓存中。接着,Spring会回到代理对象,填充代理对象的属性,然后将代理对象放入到earlySingletonObjects缓存中。当代理对象的属性填充完成后,Spring会将代理对象替换成真正的Bean对象,然后将真正的Bean对象放入到singletonObjects缓存中,并清除earlySingletonObjects缓存和singletonFactories缓存中的代理对象。

Spring使用三级缓存创建bean的过程

Spring使用三级缓存创建Bean的详细过程如下:

  1. 首先,Spring会检查该Bean是否已经创建并存储在singletonObjects缓存中。如果Bean已经存在,则直接返回该Bean实例。这是最快的情况。
  2. 如果Bean不存在于singletonObjects缓存中,Spring会检查该Bean是否正在创建中。如果正在创建中,则返回一个代理对象,该代理对象会在真正的Bean创建完成后被替换成真正的Bean对象。这是为了防止循环依赖的情况。
  3. 如果该Bean既不存在于singletonObjects缓存中,也没有正在创建中,则Spring会创建一个ObjectFactory,该ObjectFactory负责创建该Bean实例。ObjectFactory是一个工厂方法,它负责创建和返回Bean实例。
  4. 然后,Spring会将该ObjectFactory存储到singletonFactories缓存中,以便下次获取该Bean实例时使用。这是为了提高创建Bean实例的效率。
  5. 接着,Spring会检查该Bean是否依赖于其他Bean。如果依赖于其他Bean,则会先创建依赖的Bean实例。依赖项可以是其他Bean,也可以是基本类型或集合。
  6. 如果依赖的Bean实例还不存在,则Spring会递归地创建依赖的Bean实例,直到所有依赖的Bean实例都已经创建完成。这是为了保证依赖关系正确。
  7. 如果所有依赖的Bean实例都已经创建完成,则Spring会从singletonFactories缓存中获取该Bean的ObjectFactory,并使用该ObjectFactory创建该Bean实例。这是Bean实例的创建过程。
  8. 创建该Bean实例后,Spring会将该Bean实例存储到earlySingletonObjects缓存中,以便后续填充该Bean的属性。earlySingletonObjects缓存包含已经创建的单例Bean实例,但是还没有填充属性。
  9. 接着,Spring会递归地填充该Bean的属性。如果该Bean的属性依赖于其他Bean,则会先创建依赖的Bean实例。填充属性之前,Spring会调用所有实现了BeanPostProcessor接口的类的postProcessBeforeInitialization()方法做一些预处理工作。这是为了提供一个扩展点,可以在Bean初始化之前对Bean进行定制。
  10. 如果所有依赖的Bean实例都已经创建完成,则Spring会从singletonObjects缓存中获取依赖的Bean实例,并将依赖的Bean实例填充到该Bean的属性中。填充属性之后,Spring会再次调用所有实现了BeanPostProcessor接口的类的postProcessAfterInitialization()方法做一些后处理工作。这是为了提供一个扩展点,可以在Bean初始化之后对Bean进行定制。
  11. 填充该Bean的属性后,Spring会将该Bean实例存储到singletonObjects缓存中,并从earlySingletonObjects缓存中移除该Bean实例。singletonObjects缓存包含已经创建的单例Bean实例,并已经填充了属性。
  12. 最后,Spring会递归地处理该Bean的后置处理器,然后返回该Bean实例。如果Bean实现了InitializingBean接口,Spring会调用其afterPropertiesSet()方法,这是Bean初始化之后的最后一步。如果Bean实现了DisposableBean接口,Spring会在Bean销毁之前调用其destroy()方法。

Spring使用AOP

基本过程如下:

  1. 在配置类上添加@EnableAspectJAutoProxy注解,启用AspectJ自动代理。
  2. 创建一个切面类,并且在该类上使用@Aspect注解来标识该类为切面类。
  3. 在切面类中定义一个或多个切点,用来匹配需要拦截的方法。
  4. 在切面类中定义一个或多个通知,用来在方法执行前、执行后或抛出异常时执行一些额外的逻辑。
  5. 将切面类添加到Spring的容器中。

以下是一个使用注解方式实现AOP的示例:

@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(* com.example.demo.service.*.*(..))")
    public void serviceLayer(){}

    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before " + joinPoint.getSignature().getName());
    }

    @After("serviceLayer()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("After " + joinPoint.getSignature().getName());
    }
}

@Service
public class DemoService {
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example.demo")
public class AppConfig {
}

在上面的示例中,LoggingAspect是一个切面类,它定义了一个切点serviceLayer()和两个通知logBefore()和logAfter()。serviceLayer()用来匹配com.example.demo.service包中所有方法,logBefore()和logAfter()分别在方法执行前和执行后输出一条日志。DemoService是一个普通的服务类,它的doSomething()方法会被LoggingAspect拦截。最后,AppConfig是一个配置类,它启用了AspectJ自动代理,并将LoggingAspect和DemoService添加到Spring的容器中。

当调用DemoService的doSomething()方法时,LoggingAspect会拦截该方法,并在方法执行前输出一条日志,同时在方法执行后输出一条日志。

SpringAOP的实现原理

Spring AOP的实现原理是使用动态代理技术。动态代理是在运行时创建代理对象,代理对象实现了目标对象的接口,并且拦截所有方法调用。Spring AOP使用了两种类型的代理:JDK动态代理和CGLIB动态代理。如果目标对象实现了接口,则使用JDK动态代理;否则,使用CGLIB动态代理。

Spring使用事务管理

Spring的事务管理可以通过如下方式来使用:

  1. 配置事务管理器:Spring提供了多种事务管理器,如DataSourceTransactionManager、HibernateTransactionManager等。可以通过配置事务管理器来选择适合的事务管理器。
  2. 配置事务属性:可以通过配置事务属性来指定事务的传播行为、隔离级别、超时时间、只读等特性。
  3. 使用@Transactional注解:可以在需要事务管理的方法上添加@Transactional注解,Spring会自动管理该方法的事务。@Transactional注解可以指定事务的传播行为、隔离级别、超时时间、只读等特性。

Spring事务的传播行为指的是当一个事务方法调用另外一个事务方法时,如何管理事务。Spring定义了七种传播行为,包括:

  • REQUIRED:如果当前存在事务,则加入该事务,否则创建一个新的事务。
  • SUPPORTS:如果当前存在事务,则加入该事务,否则以非事务的方式执行。
  • MANDATORY:必须在一个已有的事务中执行,否则抛出异常。
  • REQUIRES_NEW:创建一个新的事务,并且暂停当前事务(如果存在)。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则暂停该事务。
  • NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。

Spring事务的隔离级别指的是多个事务之间的隔离程度。Spring定义了五种隔离级别,包括:

  • DEFAULT:使用底层数据库的默认隔离级别。
  • READ_UNCOMMITTED:允许脏读、不可重复读和幻读。
  • READ_COMMITTED:禁止脏读,但允许不可重复读和幻读。
  • REPEATABLE_READ:禁止脏读和不可重复读,但允许幻读。
  • SERIALIZABLE:禁止脏读、不可重复读和幻读,最严格的隔离级别。

Spring事务的超时时间指的是事务的执行时间超过该时间时,事务将被回滚。Spring默认的事务超时时间为-1,表示事务没有超时限制。只读事务指的是事务中只读取数据,不修改数据。只读事务可以提高数据库的并发性能。

可以使用@Transactional注解来指定事务的传播行为、隔离级别、超时时间、只读等特性。例如:

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 3600, readOnly = false)
public void doSomething() {
    // some code
}

在这个例子中,@Transactional注解指定了事务的传播行为为REQUIRED、隔离级别为DEFAULT、超时时间为3600秒、只读为false。

使用Spring事务管理可以提高应用程序的性能和可靠性。它可以确保在事务范围内的所有操作要么全部成功,要么全部失败,从而保持数据的完整性和一致性。

Spring事务的原理

Spring事务的实现原理是使用AOP技术。在Spring中,事务管理是通过将事务管理器和事务通知应用到目标方法上来实现的。事务管理器是负责管理事务的对象,事务通知是负责在目标方法执行前后开启、提交或回滚事务的对象。在Spring中,事务通知是使用AspectJ的@Aspect注解来实现的。

当一个方法被标记为@Transactional时,Spring会创建一个代理对象来代理该方法。该代理对象会将目标方法的调用转发给事务通知。在事务通知中,会根据事务管理器的配置来开启、提交或回滚事务。如果方法执行过程中发生了异常,则会回滚事务。如果方法执行成功,则会提交事务。事务管理器会将事务的状态存储在ThreadLocal中,以便在同一线程中的其他方法也可以访问该事务。

Spring事务的实现依赖于底层的数据库事务。Spring提供了多种事务管理器,如DataSourceTransactionManager、HibernateTransactionManager等。事务管理器可以配置事务的传播行为、隔离级别、超时时间、只读等特性。

DataSourceTransactionManager的实现

DataSourceTransactionManager的实现主要包括以下内容:

  1. 实现了PlatformTransactionManager接口,用于管理事务的生命周期。PlatformTransactionManager接口是Spring事务管理器的核心接口,它定义了事务的生命周期和状态转换规则。DataSourceTransactionManager实现了PlatformTransactionManager接口,用于管理基于DataSource的事务。PlatformTransactionManager接口包括以下方法:
    • getTransaction(TransactionDefinition definition):获取一个新的事务或将当前线程中的事务加入到当前方法的事务中。
    • commit(TransactionStatus status):提交当前事务。
    • rollback(TransactionStatus status):回滚当前事务。
  2. 实现了TransactionDefinition接口,用于定义事务的传播行为、隔离级别、超时时间、只读等特性。TransactionDefinition接口定义了事务的属性,包括传播行为、隔离级别、超时时间、只读等。DataSourceTransactionManager实现了TransactionDefinition接口,用于指定基于DataSource的事务的属性。
  3. 实现了TransactionStatus接口,用于跟踪事务的状态。TransactionStatus接口定义了事务的状态,包括未提交、已提交和已回滚。DataSourceTransactionManager实现了TransactionStatus接口,用于跟踪基于DataSource的事务的状态。
  4. 实现了TransactionSynchronizationManager类,用于管理事务的同步状态。TransactionSynchronizationManager类用于管理事务的同步状态,包括事务的开始、结束和回滚等操作。DataSourceTransactionManager使用TransactionSynchronizationManager类来管理基于DataSource的事务的同步状态。
  5. 实现了TransactionAspectSupport类,用于支持基于AspectJ的事务通知。TransactionAspectSupport类是Spring事务管理的核心类,它实现了基于AspectJ的事务通知逻辑。DataSourceTransactionManager使用TransactionAspectSupport类来支持基于AspectJ的事务通知。

DataSourceTransactionManager的工作流程

  1. 当一个方法被标记为@Transactional时,Spring会创建一个代理对象来代理该方法。
  2. 代理对象会调用TransactionAspectSupport类的invokeWithinTransaction()方法,该方法会在事务的上下文中执行目标方法。
  3. 在invokeWithinTransaction()方法中,会调用DataSourceTransactionManager的doBegin()方法来开启事务。
  4. 在doBegin()方法中,会获取当前线程中的Connection对象,并将其设置为事务的连接。
  5. 如果当前线程中没有Connection对象,则会从DataSource中获取一个新的Connection对象,并将其设置为事务的连接。
  6. 如果事务的隔离级别不是默认值,则会将事务的隔离级别设置到Connection对象上。
  7. 如果事务的超时时间不是默认值,则会将事务的超时时间设置到Connection对象上。
  8. 在目标方法执行完毕后,会调用TransactionAspectSupport类的invokeWithinTransaction()方法的afterCompletion()方法来提交或回滚事务。
  9. 在afterCompletion()方法中,会调用DataSourceTransactionManager的doCleanupAfterCompletion()方法来清理事务状态。
  10. 在doCleanupAfterCompletion()方法中,会将事务的连接关闭,并将其从当前线程中移除。

Spring不太常用的注解

  • @Lazy:指定Bean是否延迟初始化。默认情况下,Spring会在容器启动时创建所有的单例Bean,通过设置@Lazy注解可以将Bean的创建推迟到第一次使用时。这对于那些启动时间较长的应用程序来说非常有用,可以提高应用程序的启动速度。
  • @DependsOn:指定Bean依赖的其他Bean。如果被依赖的Bean未被创建,则会先创建该Bean。@DependsOn注解可以用来控制Bean的创建顺序,确保依赖的Bean在当前Bean之前被创建。
  • @Primary:在多个Bean实现某个接口时,指定默认使用哪个Bean。当一个接口有多个实现类时,可以使用@Primary注解来指定默认使用哪个实现类。如果没有指定@Primary注解,则需要使用@Qualifier注解来指定使用哪个实现类。
  • @Qualifier:指定使用哪个Bean实现某个接口。当有多个Bean实现同一个接口时,可以使用@Qualifier注解来指定使用哪个Bean。@Qualifier注解需要与@Autowired注解一起使用,在注入Bean时指定使用哪个实现类。
  • @Profile:指定Bean在特定的环境中才会被创建。可以使用@Profile注解来指定Bean在哪些环境下被创建,例如开发环境、测试环境或生产环境。
  • @Value:从属性文件中获取Bean的属性值。可以使用@Value注解来指定Bean的属性值。@Value注解可以用来注入简单类型的值,例如字符串、数字或布尔值。
  • @RestController:将一个类声明为RESTful Web服务的控制器。可以使用@RestController注解来将一个类声明为RESTful Web服务的控制器,使其能够处理HTTP请求并返回JSON或XML格式的数据。
  • @ExceptionHandler:处理异常。可以使用@ExceptionHandler注解来处理Controller中的异常,当Controller中抛出异常时,会自动调用@ExceptionHandler注解中指定的方法来处理异常。

成功是一个长期的过程,需要不断地努力和坚持。

  • “成功的秘诀在于坚持,坚持,再坚持。”——张德芬
  • “成功不是将来才有的,而是从决定去做的那一刻起,持续累积而成。”——阿斯顿·马丁
  • “只有在经历了漫长跋涉之后,才能登上理想的山巅。”——菲茨杰拉德
  • “成功的关键在于我们是否真正热爱我们所做的事情,是否做到了最好。”——贺绿汀
  • “成功者不是从不失败,而是能够从失败中振作起来。”——乔治·爱德华·伯纳德·肖
  • “不要害怕失败,失败是通向成功的必经之路。”——迈克尔·乔丹

成功不是一蹴而就的,它需要我们一步一个脚印地向前走,不断地尝试和学习,不断地改进和完善。在这个过程中,我们可能会遇到挫折和困难,但是只要我们保持信心和勇气,坚持不懈地努力,最终我们一定会迎来成功的喜悦。