Spring题库详解

目录

3、Spring Beans

3.1、什么是Spring beans?

3.2、 一个 Spring Bean 定义包含什么?

3.3、如何给Spring 容器提供配置元数据?Spring有几种配置方式

3.4、 Spring配置文件包含了哪些信息

3.5. Spring基于xml注入bean的几种方式

3.5. 1、Bean的配置方式

3.5. 2、依赖注入的方式

3.5. 3、工厂方法注入

3.5. 4、引用其他Bean

3.5. 5、引用内部Bean

3.5. 6、级联属性

3.5. 7、集合属性

​编辑

3.5.8、使用p命名空间 

 3.5.9、通过C名称空间来简化我们构造方法的属性赋值 

3.5.10、使用autowire属性进行Bean的自动装配

​编辑

​编辑

​编辑

​编辑

3.5.11、配置上的继承(parent属性)

3.5.12、Bean的作用域(scope属性)

​编辑

3.5.13、使用外部属性文件(property-placeholder 属性占位符)

3.5.14 使用外部属性文件(property-placeholder 属性占位符)

3.5.15 Spring表达式SPEL表达式

3.5.16  Spring中Bean的生命周期 

3.6. 你怎样定义类的作用域?

3.7. 解释Spring支持的几种bean的作用域

3.8、 Spring框架中的单例bean是线程安全的吗?

3.9、 Spring如何处理线程并发问题?

3.10. 解释Spring框架中bean的生命周期

3.11. 哪些是重要的bean生命周期方法? 你能重载它们吗?

3.12.什么是Spring的内部bean?什么是Spring inner beans?

3.13 在 Spring中如何注入一个java集合?

3.14 什么是bean装配?

3.15 什么是bean的自动装配?

3.16  解释不同方式的自动装配,spring 自动装配 bean 有哪些方式?

3.17  使用@Autowired注解自动装配的过程是怎样的?

3.18  自动装配有哪些局限性?

3.19  你可以在Spring中注入一个null 和一个空字符串吗?

4、Spring注解

4.1. 什么是基于Java的Spring注解配置? 给一些注解的例子

4.2. 怎样开启注解装配?

3. @Component, @Controller, @Repository, @Service 有何区别?

4. @Required 注解有什么作用

5. @Autowired 注解有什么作用

6. @Autowired和@Resource之间的区别

7. @Qualifier 注解有什么作用

8. @RequestMapping 注解有什么用?

5、Spring数据访问

5.1、 解释对象/关系映射集成模块

5.2、  在Spring框架中如何更有效地使用JDBC?

5.3、 解释JDBC抽象和DAO模块

5.4、  spring DAO 有什么用?

5.5、 spring JDBC API 中存在哪些类?

5.6、JdbcTemplate是什么

5.6.1、JdbcTemplate(概念和准备)

5.6.2、JdbcTemplate 操作数据库(添加)

5.6.3、JdbcTemplate 操作数据库(删、改)——只需修改以下几个文件

5.6.4、JdbcTemplate 查询操作

5.7. 使用Spring通过什么方式访问Hibernate?

5.7.1.使用Spring的HibernateTemplate:

5.7.2.使用Spring的HibernateDaoSupport:

5.7.3.使用Spring的JpaTransactionManager:

5.7.4.使用Spring的注解驱动方式:

5.7.5.使用Spring Data JPA:

5.8 如何通过HibernateDaoSupport将Spring和Hibernate结合起来?

5.9、Spring支持的事务管理类型, spring 事务实现方式有哪些?

5.10、Spring事务的实现方式和实现原理

5.11 说一下Spring的事务传播行为

5.12. 说一下 spring 的事务隔离?

5.13、Spring框架的事务管理有哪些优点?

5.13.1、Spring事务实现方式

5.13.2、Spring编程式事务

5.13.3、Spring声明式事务

5.13.4、声明式事务注解方式

5.13.5、@Transactional注解的常用参数包括:

5.13.6、事务注解失效情况

5.13.7、什么是Spring事务传播机制

5.13.8、Spring事务的隔离级别

5.13.9、rollbackFor属性

6、Spring面向切面编程(AOP)

6.1、 什么是AOP

6.2、Spring AOP and AspectJ AOP 有什么区别?

6.3、JDK动态代理和CGLIB动态代理的区别

6.4、 如何理解 Spring 中的代理?

6.4.1、 JDK 动态代理

6.4.2、CGLIB(Code Generation Library)代理:

6.5、 解释一下Spring AOP里面的几个名词

6.5.1、连接点(Joinpoint)

6.6、Spring在运行时通知对象

6.7、Spring只支持方法级别的连接点

6.7.1、Spring AOP支持的Aspect切入点指示符如下:

6.7.2、命名及匿名切入点

6.7.3、类型匹配语法

6.7.4、组合切入点表达式

6.8、 在Spring AOP 中,关注点和横切关注的区别是什么?

6.9、Spring通知有哪些类型?

6.10、什么是切面 Aspect?

6.11、 解释基于XML Schema方式的切面实现

6.12、解释基于注解的切面实现

6.13、有几种不同类型的自动代理?


1 spring 概述

1.1、什么是spring?

Spring是一个轻量级Java开发框架,最早有Rod Johnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。

Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。

Spring可以做很多事情,它为企业级开发提供给了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)。

为了降低Java开发的复杂性,Spring采取了以下4种关键策略

基于POJO的轻量级和最小侵入性编程;

通过依赖注入和面向接口实现松耦合;

基于切面和惯例进行声明式编程;

通过切面和模板减少样板式代码。

1.2、Spring框架的设计目标,设计理念,和核心是什么?

Spring设计目标:Spring为开发者提供一个一站式轻量级应用开发平台;

Spring设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OO(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦;

Spring框架的核心:IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

1.3、Spring的优缺点是什么?

优点:

1)方便解耦,简化开发 Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。

2)AOP编程的支持 Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。

3)声明式事务的支持 只需要通过配置就可以完成对事务的管理,而无需手动编程。

4)方便程序的测试 Spring对Junit4支持,可以通过注解方便的测试Spring程序。

5)方便集成各种优秀框架 Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。

6)降低JavaEE API的使用难度 Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

缺点: - Spring明明一个很轻量级的框架,却给人感觉大而全 - Spring依赖反射,反射影响性能 - 使用门槛升高,入门Spring需要较长时间

1.4、 Spring有哪些应用场景

应用场景:JavaEE企业应用开发,包括SSH、SSM等

Spring价值:

Spring是非侵入式的框架,目标是使应用程序代码对框架依赖最小化; Spring提供一个一致的编程模型,使应用直接使用POJO开发,与运行环境隔离开来; Spring推动应用设计风格向面向对象和面向接口开发转变,提高了代码的重用性和可测试性;

1.5、Spring由哪些模块组成?

Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。 以下是 Spring 5 的模块结构图:

spring core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。

spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。

spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。

spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。

spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。

spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。

spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。

1.6、Spring 框架中都用到了哪些设计模式?

工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例; 单例模式:Bean默认为单例模式。 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术; 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

1.7、详细讲解一下核心容器(spring context应用上下文) 模块

这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是 任何以spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。

Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。

1.8、Spring框架中有哪些不同类型的事件

Spring 提供了以下5种标准的事件:

上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

1.9、 Spring 应用程序有哪些不同组件?

Spring 应用一般有以下组件:

接口 - 定义功能。 Bean 类 - 它包含属性,setter 和 getter 方法,函数等。 Bean 配置文件 - 包含类的信息以及如何配置它们。 Spring 面向切面编程(AOP) - 提供面向切面编程的功能。 用户程序 - 它使用接口。

1.10、 使用 Spring 有哪些方式?

使用 Spring 有以下方式:

作为一个成熟的 Spring Web 应用程序。 作为第三方 Web 框架,使用 Spring Frameworks 中间层。 作为企业级 Java Bean,它可以包装现有的 POJO(Plain Old Java Objects)。 用于远程使用。

2、Spring控制反转(IOC)

2.1. 什么是Spring IOC 容器?

控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。

Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。

2.2 控制反转(IoC)有什么作用

管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的 解耦,由容器去维护具体的对象 托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的

2.3  IOC的优点是什么?

IOC 或 依赖注入把应用的代码量降到最低。 它使应用容易测试,单元测试不再需要单例和JNDI查找机制。 最小的代价和最小的侵入性使松散耦合得以实现。 IOC容器支持加载服务时的饿汉式初始化和懒加载。

 2.4  Spring IoC 的实现机制 

Spring 中的 IoC 的实现原理就是工厂模式加反射机制。

示例:

interface Fruit {
   public abstract void eat();
 }

class Apple implements Fruit {
    public void eat(){
        System.out.println("Apple");
    }
}

class Orange implements Fruit {
    public void eat(){
        System.out.println("Orange");
    }
}

class Factory {
    public static Fruit getInstance(String ClassName) {
        Fruit f=null;
        try {
            f=(Fruit)Class.forName(ClassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return f;
    }
}

class Client {
    public static void main(String[] a) {
        Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
        if(f!=null){
            f.eat();
        }
    }
}

2.5  Spring 的 IoC支持哪些功能

Spring 的 IoC 设计支持以下功能:

依赖注入 依赖检查 自动装配 支持集合 指定初始化方法和销毁方法 支持回调某些方法(但是需要实现 Spring 接口,略有侵入) 其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象。

对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。

6. BeanFactory 和 ApplicationContext有什么区别?

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

依赖关系

BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。

ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

继承MessageSource,因此支持国际化。 统一的资源文件访问方式。 提供在监听器中注册bean的事件。 同时加载多个配置文件。 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。 加载方式

BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

创建方式

BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

注册方式

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

7. Spring 如何设计容器的,BeanFactory和ApplicationContext的关系详解

Spring 作者 Rod Johnson 设计了两个接口用以表示容器。

BeanFactory ApplicationContext BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。

ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。

当然,除了这两个大接口,还有其他的辅助接口,这里就不介绍他们了。

BeanFactory和ApplicationContext的关系

为了更直观的展示 “低级容器” 和 “高级容器” 的关系,这里通过常用的 ClassPathXmlApplicationContext 类来展示整个容器的层级 UML 关系。

8. ApplicationContext通常的实现是什么?

FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。

ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。

WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。

9. 什么是Spring的依赖注入?

控制反转IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找

依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。

10. 依赖注入的基本原则

依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象。

11. 依赖注入有什么优势

依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口,使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比,主要优势为:

查找定位操作与应用代码完全无关。 不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。 不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。

12. 有哪些不同类型的依赖注入实现方式?

依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。

构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。

Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。

13. 构造器依赖注入和 Setter方法注入的区别

构造函数注入 setter 注入 没有部分注入 有部分注入 不会覆盖 setter 属性 会覆盖 setter 属性 任意修改都会创建一个新实例 任意修改不会创建一个新实例 适用于设置很多属性 适用于设置少量属性 两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。

3、Spring Beans

3.1、什么是Spring beans?

Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中 的形式定义。

3.2、 一个 Spring Bean 定义包含什么?

一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。

3.3、如何给Spring 容器提供配置元数据?Spring有几种配置方式

这里有三种重要的方法给Spring 容器提供配置元数据。

XML配置文件。
基于注解的配置。
基于java的配置。


Spring提供了多种配置方式来向容器提供配置元数据。以下是几种常用的配置方式:

XML配置:使用XML文件进行配置是Spring最早引入的方式。通过在XML文件中定义元素和相关属性,可以指定bean的类、属性、依赖关系等。XML配置方式可以在应用程序的classpath中使用applicationContext.xml或自定义的XML文件进行配置。

注解配置:使用注解来配置Spring容器是一种更现代和简洁的方式。通过在类或方法上使用注解,可以指定bean的作用域、依赖关系、初始化方法、销毁方法等。常用的注解包括 @Component、@Service、@Repository、@Autowired等。可以通过在配置类上使用@Configuration注解将Java类声明为配置类,进一步使用注解进行配置。

Java配置:使用Java代码来配置Spring容器是一种类型安全且可编程的方式。通过编写Java配置类,可以使用Spring提供的配置API来定义和配置bean。常用的配置类是@Configuration注解标记的类,可以使用@Bean注解定义bean,并在方法中进行配置。Java配置方式可以与注解配置相结合,提供更灵活的配置选项。

3.4、 Spring配置文件包含了哪些信息

 Spring配置文件包含了哪些信息

Spring配置文件通常是一个XML文件,包含了如何定义和配置Spring应用中bean的指令。这些信息包括:

Bean的定义:包括bean的ID、类名称、作用域(singleton、prototype等)、是否懒加载等。

依赖注入:包括构造器注入、setter方法注入、字段注入等。

装配其他配置文件。

定义和配置数据源、事务管理器等。

AOP配置:包括切面、通知等。

以下是一个简单的Spring配置文件示例,包含了定义bean和依赖注入

<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 -->
    <bean id="exampleBean" class="com.example.ExampleClass">
        <!-- 使用构造器注入 -->
        <constructor-arg ref="anotherBean"/>
        <!-- 使用setter方法注入 -->
        <property name="propertyName" value="propertyValue"/>
    </bean>
 
    <!-- 定义另一个bean -->
    <bean id="anotherBean" class="com.example.AnotherClass"/>
 
</beans>

3.5. Spring基于xml注入bean的几种方式

Spring配置Bean有两种形式(XML和注解),通过XML方式配置Bean

3.5. 1、Bean的配置方式

通过全类名(反射)的方式

id:标识容器中的bean。id唯一

class:bean的全类名,通过反射的方式在IOC容器中创建Bean,所以要求Bean中必须有无参的构造器

3.5. 2、依赖注入的方式

属性注入:通过setter方法注入Bean的属性值或依赖的对象

属性注入使用<Property>元素,使用name指定Bean的属性名称,使用value指定Bean的属性的值
 

构造器注入:通过构造方法注入Bean的属性和值
首先创建一个Car类

 

package com.dk.spring.beans;

public class Car {
	private String brand;
	private int speed;
	private String color;
	public int price;
	
	public Car(String brand, int speed, String color,int price) {
		System.out.println("Car...初始化有参构造器");
		this.brand = brand;
		this.speed = speed;
		this.color = color;
		this.price = price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

	@Override
	public String toString() {
		return "Car [brand=" + brand + ", speed=" + speed + ", color=" + color + ", price=" + price + "]";
	}

}

然后加入配置文件配置节点:applicationContext.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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="helloworld" class="com.dk.spring.beans.HelloWorld">
		<property name="name" value="Spring"></property>
	</bean>
	
	<bean id="car" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi"></constructor-arg>
		<constructor-arg value="120"></constructor-arg>
		<constructor-arg value="black"></constructor-arg>
		<constructor-arg value="1000"></constructor-arg>
	</bean>

</beans>

编写测试类SpringBeanConfig.java,运行main方法开启Spring容器并使用

package com.dk.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.dk.spring.beans.Address;
import com.dk.spring.beans.Car;
import com.dk.spring.beans.OtherPerson;
import com.dk.spring.beans.Person;
import com.dk.spring.beans.RichPerson;

public class SpringBeanConfig {

	public static void main(String[] args) {
		// 1.创建Spring 的IOC的容器对象
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		        
		// 2.从IOC容器中获取bean实例
		Car car = (Car)ctx.getBean(Car.class);
		        
		// 3、调用hello方法
		System.out.println(car);

        }
}

 如果想给指定属性顺序赋值,可以使用index属性
 

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="helloworld" class="com.dk.spring.beans.HelloWorld">
		<property name="name" value="Spring"></property>
	</bean>

	<bean id="car" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi" index="0"></constructor-arg>
		<constructor-arg value="120" index="1"></constructor-arg>
		<constructor-arg value="black" index="2"></constructor-arg>
		<constructor-arg value="1000" index="3"></constructor-arg>
	</bean>

</beans>

     如果想给指定属性顺序赋值,也可以使用type属性
复制 

 

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="helloworld" class="com.dk.spring.beans.HelloWorld">
		<property name="name" value="Spring"></property>
	</bean>

	<bean id="car" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi"  type="java.lang.String"></constructor-arg>
		<constructor-arg value="120"   type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="1000"  type="int"></constructor-arg>
	</bean>

</beans>

备注:改变参数配置顺序,会影响赋值顺序,【赋值会根据同一类型出现的顺序依次赋值】,如下图所示
所以这种方式注入的话要注意赋值类型顺序。

3.5. 3、工厂方法注入

静态工厂方法注入bean
利用静态工厂方法可以把bean注入到IOC容器中。在XML文件中配置bean时,要指定class的属性为工厂的类;factory-method属性指定工厂类中工厂方法,用于创建bean;constrctor-arg用于给工厂方法传递参数。实例如下:

ExampleBean.java

package com.dk.spring.beans;

public class ExampleBean {
	
	private String name;
	
	public ExampleBean() {

		System.out.println("ExamleBean的无参构造器...");
	}
	
	public ExampleBean(String name) {
		System.out.println("ExamleBean的有参构造器...");
		this.name = name;
	}

	public static ExampleBean getInstance(String name){
		return new ExampleBean(name);
	}

	@Override
	public String toString() {
		return "ExampleBean [name=" + name + "]";
	}

}
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="helloworld" class="com.dk.spring.beans.HelloWorld">
		<property name="name" value="Spring"></property>
	</bean>

	<!--class要写工厂的类;factory-method要写工厂类中用于创建bean的方法-->
	<!-- 
		使用静态工厂方法创建一个对象
		factory-method属性:指定要调用的静态方法。
	 -->
	<bean id="exampleBean" class="com.dk.spring.beans.ExampleBean" 
	factory-method="getInstance">
		<constructor-arg value="Jack"></constructor-arg>
	</bean>
</beans>

实例工厂方法注入bean
实例化工厂方法注入bean,需要先实例化一个工厂类,然后通过由实例化工厂对象中的一个方法来创建bean,并注入到容器中。 
在 bean 的 factory-bean 属性里指定拥有该工厂方法的 Bean;在 factory-method 属性里指定该工厂方法的名称;使用 construtor-arg 元素为工厂方法传递方法参数。示例如下

 

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="helloworld" class="com.dk.spring.beans.HelloWorld">
		<property name="name" value="Spring"></property>
	</bean>

    <!-- 使用静态工厂方法创建一个对象 factory-method属性:指定要调用的静态方法。 -->
	<!--class要写工厂的类;factory-method要写工厂类中用于创建bean的方法 -->
	<bean id="exampleBean" class="com.dk.spring.beans.ExampleBean"
		factory-method="getInstance">
		<constructor-arg value="Jack"></constructor-arg>
	</bean>
	

	<!-- 使用实例工厂方法创建一个对象 factory-bean属性:要调用的对象的id。 factory-method属性:要调用的方法。 -->
	<bean id="calendar" class="java.util.GregorianCalendar" />
	<bean id="time" factory-bean="calendar" factory-method="getTime" />
	
	 <bean id="carFactory" class="com.dk.spring.beans.CarFactory"></bean>
	 <bean id="car" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="baoma"></constructor-arg>
        <constructor-arg value="102"></constructor-arg>
        <constructor-arg value="baoma"></constructor-arg>
        <constructor-arg value="100000"></constructor-arg>
    </bean>

</beans>

CarFactory.java
 

package com.dk.spring.beans;

public class CarFactory {

	public Car getCar(String brand, int speed, String color,int price){
		return new Car(brand, speed, color, price);
	}

}

配置bean的属性时可以 使用ref引用其他配置好的bean对象,ref的值为引用bean的id
构造器方式<constructor-arg  ref="car"></constructor-arg>
属性注入<property name="name"  ref=""></property>

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="helloworld" class="com.dk.spring.beans.HelloWorld">
		<property name="name" value="Spring"></property>
	</bean>

    <!-- 使用静态工厂方法创建一个对象 factory-method属性:指定要调用的静态方法。 -->
	<!--class要写工厂的类;factory-method要写工厂类中用于创建bean的方法 -->
	<bean id="exampleBean" class="com.dk.spring.beans.ExampleBean"
		factory-method="getInstance2">
		<constructor-arg  ref="car"></constructor-arg>
	</bean>
	

	<!-- 使用实例工厂方法创建一个对象 factory-bean属性:要调用的对象的id。 factory-method属性:要调用的方法。 -->
	<bean id="calendar" class="java.util.GregorianCalendar" />
	<bean id="time" factory-bean="calendar" factory-method="getTime" />
	
	 <bean id="carFactory" class="com.dk.spring.beans.CarFactory"></bean>
	 <bean id="car" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="baoma"></constructor-arg>
        <constructor-arg value="102"></constructor-arg>
        <constructor-arg value="baoma"></constructor-arg>
        <constructor-arg value="100000"></constructor-arg>
    </bean>

</beans>
package com.dk.spring.beans;

public class ExampleBean {
	
	private String name;
	private Car car;
	
	public ExampleBean(Car car) {
		System.out.println("ExamleBean的有参构造器...");
		this.car = car;
	}
	public static ExampleBean getInstance2(Car car){
		return new ExampleBean(car);
	}
	
	public ExampleBean() {

		System.out.println("ExamleBean的无参构造器...");
	}
	
	public ExampleBean(String name) {
		System.out.println("ExamleBean的有参构造器...");
		this.name = name;
	}

	public static ExampleBean getInstance(String name){
		return new ExampleBean(name);
	}

	@Override
	public String toString() {
		return "ExampleBean [name=" + name + ", car=" + car + "]";
	}

}

 工厂方法方式在实际的项目中很少使用,使用最多的是属性注入、构造器注入。

3.5. 4、引用其他Bean

引用外部Bean
使用 property的ref属性建立Bean之间的引用关系

package com.dk.spring.beans;

public class Person {

	private String name;
	private int age;
	private Car car;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Car getCar() {
		return car;
	}
	public void setCar(Car car) {
		this.car = car;
	}
		
}
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="car" class="com.dk.spring.beans.Car">
	    <constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
	    <constructor-arg value="120" type="int"></constructor-arg>
	    <constructor-arg value="black" type="java.lang.String"></constructor-arg>
	    <constructor-arg value="100000"></constructor-arg>
	</bean>
	
	<bean id="person" class="com.dk.spring.beans.Person">
	    <property name="name" value="dk"></property>
	    <property name="age" value="32"></property>
	    <!-- 使用 property的ref属性建立Bean之间的引用关系-->
	    <property name="car" ref="car"></property>
	</bean>

</beans>

3.5. 5、引用内部Bean

     因为id是为了让外部引用,而内部Bean内部只用一次,不能被外部引用,所以不写id。
 

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="person" class="com.dk.spring.beans.Person">
		<property name="name" value="dk"></property>
		<property name="age" value="32"></property>
		<!-- 内部Bean,不能被外部引用,只能在内部使用 -->
		<property name="car">
			<bean class="com.dk.spring.beans.Car">
				<constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
				<constructor-arg value="120" type="int"></constructor-arg>
				<constructor-arg value="black" type="java.lang.String"></constructor-arg>
				<constructor-arg value="100000"></constructor-arg>
			</bean>
		</property>
	</bean>
</beans>

3.5. 6、级联属性

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

    
	<bean id="person" class="com.dk.spring.beans.Person">
	    <property name="name" value="dk"></property>
	    <property name="age" value="32"></property>
	    <property name="car" ref="car"></property>
	    <!-- 级联属性赋值 -->
	    <property name="car.price" value="30000"></property>
	</bean>
	
    <bean id="car" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi" index="0"></constructor-arg>
		<constructor-arg value="120" index="1"></constructor-arg>
		<constructor-arg value="black" index="2"></constructor-arg>
		<constructor-arg value="1000" index="3"></constructor-arg>
	</bean>
	
</beans>

 注意:属性想用car.price的级联,首先car必须要先初始化,否则会有异常
 

3.5. 7、集合属性


  当我们的Bean中有集合元素的时候,Spring中可以通过一组内置的xml标签(<list>、<set>、<map>)来配置集合属性。

List集合:
     创建RichPerson类有一个cars属性,并且RichPerson类cars属性是一个List<car>,即一个人拥有多辆车。

package com.dk.spring.beans;

import java.util.List;
import java.util.Map;

public class RichPerson {
    private String name;
    private int age;
    private List<Car> cars;
    private Map<String,Car> cars2;
    
	//为上面的4个属性设置getter和setter方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public List<Car> getCars() {
        return cars;
    }
    public void setCars(List<Car> cars) {
        this.cars = cars;
    }
	public Map<String,Car> getCars2() {
		return cars2;
	}
	public void setCars2(Map<String,Car> cars2) {
		this.cars2 = cars2;
	}
    
}

 配置文件修改如下:
 

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

    
	<bean id="car1" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
		<constructor-arg value="120" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="1000" type="int"></constructor-arg>
	</bean>
	<bean id="car2" class="com.dk.spring.beans.Car">
		<constructor-arg value="Ford" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="1000" index="3"></constructor-arg>
	</bean>
	<bean id="car3" class="com.dk.spring.beans.Car">
		<constructor-arg value="Toyota" type="java.lang.String"></constructor-arg>
		<constructor-arg value="95" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="1000" type="int"></constructor-arg>
	</bean>
	<bean id="richPerson" class="com.dk.spring.beans.RichPerson">
		<property name="name" value="dk"></property>
		<property name="age" value="32"></property>
		<!-- 集合属性(List),使用ref来配置子节点信息 -->
		<property name="cars">
			<list>
				<ref bean="car1"></ref>
				<ref bean="car2"></ref>
				<ref bean="car3"></ref>
			</list>
		</property>
	</bean>
	
</beans>

Map集合:

 如果是<map>集合的话,只需要把list换成map即可。
 

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

    
	<bean id="car1" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
		<constructor-arg value="120" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="1000" type="int"></constructor-arg>
	</bean>
	<bean id="car2" class="com.dk.spring.beans.Car">
		<constructor-arg value="Ford" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="1000" index="3"></constructor-arg>
	</bean>
	<bean id="car3" class="com.dk.spring.beans.Car">
		<constructor-arg value="Toyota" type="java.lang.String"></constructor-arg>
		<constructor-arg value="95" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="1000" type="int"></constructor-arg>
	</bean>
	<bean id="richPerson" class="com.dk.spring.beans.RichPerson">
		<property name="name" value="dk"></property>
		<property name="age" value="32"></property>
		<!-- 集合属性(List),使用ref来配置子节点信息 -->
		<property name="cars">
			<list>
				<ref bean="car1"></ref>
				<ref bean="car2"></ref>
				<ref bean="car3"></ref>
			</list>
		</property>
		
		<!-- 集合属性(Map),使用entry来配置map的子节点信息 -->
		<property name="cars2">
			<map>
				<entry key="A" value-ref="car1"></entry>
				<entry key="B" value-ref="car2"></entry>
			</map>
		</property>
	</bean>
	
</beans>

现在这么多集合Bean能不能抽出来,以供多个Bean进行引用,那么就需要使用util

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

    <bean id="car1" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
		<constructor-arg value="120" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean>
	<bean id="car2" class="com.dk.spring.beans.Car">
		<constructor-arg value="Ford" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean>
	<bean id="car3" class="com.dk.spring.beans.Car">
		<constructor-arg value="Toyota" type="java.lang.String"></constructor-arg>
		<constructor-arg value="95" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean> 
	
	<bean id="richPerson" class="com.dk.spring.beans.RichPerson">
		<property name="name" value="dk"></property>
		<property name="age" value="32"></property>
		<!-- 引用util -->
		<property name="cars" ref="cars"></property>
	</bean> 

	
	
	<util:list id="cars">
		<ref bean="car1"></ref>
		<ref bean="car2"></ref>
	</util:list> 

	
</beans>

3.5.8、使用p命名空间 


Sping从2.5版本开始引入一个新的P的命名空间,通过使用P命名空间后,使得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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

    <bean id="car1" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
		<constructor-arg value="120" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean>
	<bean id="car2" class="com.dk.spring.beans.Car">
		<constructor-arg value="Ford" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean>
	<bean id="car3" class="com.dk.spring.beans.Car">
		<constructor-arg value="Toyota" type="java.lang.String"></constructor-arg>
		<constructor-arg value="95" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean> 	
	
	<util:list id="cars">
		<ref bean="car1"></ref>
		<ref bean="car2"></ref>
	</util:list> 
	
	<!-- 通过P命名空间为Bean属性赋值,相对于传统的配置方式更加简洁 -->
	
	<bean id="richPerson" class="com.dk.spring.beans.RichPerson" p:name="dk"
		p:age="25" p:cars-ref="cars">
	</bean> 


</beans>

 3.5.9、通过C名称空间来简化我们构造方法的属性赋值 

package com.dk.spring.beans;

import java.util.List;
import java.util.Map;

public class RichPerson {
    private String name;
    private int age;
    private List<Car> cars;
    private Map<String,Car> cars2;
    
    //构造函数(C命名空间使用)
    public RichPerson(String name, int age, List<Car> cars) {
		System.out.println("RichPerson有参构造器初始化。。。。");
		this.name = name;
		this.age = age;
		this.cars = cars;
	}
    
	//为上面的4个属性设置getter和setter方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public List<Car> getCars() {
        return cars;
    }
    public void setCars(List<Car> cars) {
        this.cars = cars;
    }
	public Map<String,Car> getCars2() {
		return cars2;
	}
	public void setCars2(Map<String,Car> cars2) {
		this.cars2 = cars2;
	}
    
}
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

    <bean id="car1" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
		<constructor-arg value="120" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean>
	<bean id="car2" class="com.dk.spring.beans.Car">
		<constructor-arg value="Ford" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean>
	<bean id="car3" class="com.dk.spring.beans.Car">
		<constructor-arg value="Toyota" type="java.lang.String"></constructor-arg>
		<constructor-arg value="95" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean> 	
	
	<util:list id="cars">
		<ref bean="car1"></ref>
		<ref bean="car2"></ref>
	</util:list> 
	
	<!-- 通过C名称空间来简化我们构造方法的属性赋值 -->
	
	<bean id="richPerson" class="com.dk.spring.beans.RichPerson" c:name="dk"
		c:age="25" c:cars-ref="cars">
	</bean> 



</beans>

3.5.10、使用autowire属性进行Bean的自动装配

当我们要往一个bean的某个属性里注入另外一个bean,我们会使用<property ref=“”>标签的形式。但是对于大型项目这种方式其实是不可取的。

package com.dk.spring.beans;

public class Person {

	private String name;
	private int age;
	private Car car;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Car getCar() {
		return car;
	}
	public void setCar(Car car) {
		this.car = car;
	}
		
}
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<!-- 使用autowire属性进行Bean的自动装配 -->
	<!-- 
	     大型项目中我们一般使用autowire属性自动装备Bean,autowire属性有两个值:byName和byType
	        byName:根据Bean的名字和当前的Bean的setter属性名称进行装配 若没有,则为null
	        byType:根据Bean的类型进行装配,若有1个以上的同一类型,则抛异常
	-->
	<bean id="car" class="com.dk.spring.beans.Car">
		<constructor-arg value="Toyota" type="java.lang.String"></constructor-arg>
		<constructor-arg value="95" type="int"></constructor-arg>
		<constructor-arg value="black" type="java.lang.String"></constructor-arg>
		<constructor-arg value="100" type="int"></constructor-arg>
	</bean>
	<bean id="person" class="com.dk.spring.beans.Person" autowire="byName">
	     <property name="name" value="dk"></property>
	     <property name="age" value="32"></property>
	</bean>

</beans>

自动装配的特点:  

1)只有引用类型才可以自动装配。
2)autowire属性是在bean的级别上,一旦指定,当前bean的所有引用类型都必须使用自动装配。

3)byType和byName的只能选择其一。

byType:异常情况演示

 byName:

异常情况演示: 

3.5.11、配置上的继承(parent属性)

 当我们有两个或者以上的同一个类型的Bean做实例化的时候,我们可以将一些公共的部分提取成通用Bean。这样其他的Bean继承它即可以了。

package com.dk.spring.beans;

public class Address {

	private String provice;
	private String city;
	private String street;
	
	public String getProvice() {
		return provice;
	}
	public void setProvice(String provice) {
		this.provice = provice;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	public String getStreet() {
		return street;
	}
	public void setStreet(String street) {
		this.street = street;
	}
	
	@Override
	public String toString() {
		return "Address [provice=" + provice + ", city=" + city + ", street=" + street + "]";
	}
	
}
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
	
	<bean id="address1" class="com.dk.spring.beans.Address" p:provice="Hebei"
		p:city="LangFang" p:street="YingBing">
	</bean>
	<bean id="address2" class="com.dk.spring.beans.Address" p:provice="Hebei"
		p:city="LangFang" p:street="YanLing">
	</bean>
	<bean id="address3" class="com.dk.spring.beans.Address" p:provice="Hebei"
		p:city="LangFang" p:street="YanShun">
	</bean>  

</beans>

以上Bean中用的都是address这个类,都是省市街道的赋值,只是在街道上每个不一样,所以我们完全可以把通用的提出来。

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<!-- 	
	<bean id="address1" class="com.dk.spring.beans.Address" p:provice="Hebei"
		p:city="LangFang" p:street="YingBing">
	</bean>
	<bean id="address2" class="com.dk.spring.beans.Address" p:provice="Hebei"
		p:city="LangFang" p:street="YanLing">
	</bean>
	<bean id="address3" class="com.dk.spring.beans.Address" p:provice="Hebei"
		p:city="LangFang" p:street="YanShun">
	</bean>  
	-->
	
	<bean id="address" class="com.dk.spring.beans.Address" p:provice="Hebei"
		p:city="LangFang" p:street="YingBing" scope="prototype">
	</bean>
	<bean id="address1" parent="address" p:street="YanLing">
	</bean>
	<bean id="address2" parent="address" p:street="YanShun">
	</bean>

</beans>

3.5.12、Bean的作用域(scope属性)

输出结果为:true
说明默认为单例模式的,主要看scope属性值。
使用Bean的scope属性来配置Bean的作用域的,scope有两个重要的属性值。
singleton:默认值,容器初始化时创建Bean实例,在整个容器的生命周期内只创建一个Bean,单例的。
prototype:原型的,容器初始化时不创建Bean的实例,而在每次请求时都创建一个新的Bean的实例,并返回。

3.5.13、使用外部属性文件(property-placeholder 属性占位符)

新建一个db.properties文件

3.5.14 使用外部属性文件(property-placeholder 属性占位符)

新建一个db.properties文件
 

user=root
password=1234
driverClass=com.mysql.jdbc
jdbcUrl=jdbc:mysql:///test

MyDataSource文件:

package com.dk.spring;

public class MyDataSource {
    private String user;
    private String password;
    private String diverClass;
    private String jdbcUrl;
    
    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDiverClass() {
        return diverClass;
    }

    public void setDiverClass(String diverClass) {
        this.diverClass = diverClass;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }

    @Override
    public String toString() {
        return "MyDataSource{" +
                "user='" + user + '\'' +
                ", password='" + password + '\'' +
                ", diverClass='" + diverClass + '\'' +
                ", jdbcUrl='" + jdbcUrl + '\'' +
                '}';
    }
}


配置上属性占位符是文件的地址,并在使用的过程中使用${}的格式取值
 

<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<!-- 导入属性文件 -->
	<context:property-placeholder location="db.properties"></context:property-placeholder>
	<bean id="dataSource" class="com.dk.spring.MyDataSource">
		<!-- 使用外部属性文件的属性值 -->
		<property name="user" value="${user}"></property>
		<property name="password" value="${password}"></property>
		<property name="diverClass" value="${driverClass}"></property>
		<property name="jdbcUrl" value="${jdbcUrl}"></property>
	</bean>
	

</beans>

3.5.15 Spring表达式SPEL表达式

package com.dk.spring.beans;

public class OtherPerson {
	private Car car;
	private String city;
	private String info;
	private int yaowei;
	
	public Car getCar() {
		return car;
	}
	public void setCar(Car car) {
		this.car = car;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	public String getInfo() {
		return info;
	}
	public void setInfo(String info) {
		this.info = info;
	}
	public int getYaowei() {
		return yaowei;
	}
	public void setYaowei(int yaowei) {
		this.yaowei = yaowei;
	}
	
	@Override
	public String toString() {
		return "OtherPerson [car=" + car + ", city=" + city + ", info=" + info + ", yaowei=" + yaowei + "]";
	}
	
}
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

	<bean id="car" class="com.dk.spring.beans.Car">
		<constructor-arg value="Audi" index="0"></constructor-arg>
		<constructor-arg value="120" index="1"></constructor-arg>
		<constructor-arg value="black" index="2"></constructor-arg>
		<constructor-arg value="1000" index="3"></constructor-arg>
	</bean>
	
	<bean id="address" class="com.dk.spring.beans.Address" p:provice="Hebei"
		p:city="LangFang" p:street="YingBing">
	</bean>
	
	<!-- Spring表达式SPEL表达式 -->
	<bean id="otherPerson" class="com.dk.spring.beans.OtherPerson">
		<!-- 使用SpEL 来应用其他的Bean -->
		<property name="car" value="#{car}"></property>
		<!-- 使用SpEL 来应用其他的Bean的属性 -->
		<property name="city" value="#{address.city}"></property>
		<!-- 使用SpEL 中使用运算符 -->
		<property name="info" value="#{car.price > 30000 ? '金领' : '白领'}"></property>
		<!-- 使用SpEL 引用类的静态属性 -->
		<property name="yaowei" value="#{T(java.lang.Math).PI * 80}"></property>
	</bean>

</beans>

3.5.16  Spring中Bean的生命周期 

1)创建Bean的实例
2)为Bean的属性设置值和对其他Bean的引用
3)Bean后置处理器postProcessBeforeInitialization方法
4)调用Bean的初始化方法(init-method)
5)Bean后置处理器的postProcessAfterInitialization方法
6)Bean正常使用
7)当容器关闭时,调用Bean的销毁方法(destroy-method)  

实例:

package com.dk.spring.life;

public class Car {
    public Car() {
        System.out.println("构造器……");
    }
    private String brand;

    public void setBrand(String brand) {
        System.out.println("设置属性……");
        this.brand = brand;
    }
    public void init(){
        System.out.println("初始化……");
    }
    public void destroy(){
        System.out.println("销毁……");
    }
}

MyBeanProcessor必须实现BeanPostProcessor接口的两个方法:

package com.dk.spring.life;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
        System.out.println("postProcessBeforeInitialization……");
        return o;
    }

    @Override
    public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
        System.out.println("postProcessAfterInitialization……");
        return o;
    }
}

配置文件:
 

<?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:util="http://www.springframework.org/schema/util"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
	
	
	<bean id="car" class="com.dk.spring.life.Car" p:brand="Audi" init-method="init"
	destroy-method="destroy"></bean>
	<!-- 配置Bean的处理器 -->
	<bean class="com.dk.spring.life.MyBeanProcessor"></bean>
	
	
</beans>

测试类:

package com.dk.spring.life;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanLifeTest {

	public static void main(String[] args) {
		// 由于ApplicationContext没有close方法,所以要使用它下面子接口ConfigurableApplicationContext
		ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-beanLife.xml");
		Car car = (Car) ctx.getBean("car");
		System.out.println("使用Bean:" + car.toString());
		// 关闭IOC容器
		ctx.close();
	}

}

最终运行结果验证了上面的spring中bean的生命周期: 

3.6. 你怎样定义类的作用域?

当定义一个 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。

3.7. 解释Spring支持的几种bean的作用域

Spring框架支持以下五种bean的作用域:

singleton : bean在每个Spring ioc 容器中只有一个实例。

prototype:一个bean的定义可以有多个实例。

request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。

session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。

3.8、 Spring框架中的单例bean是线程安全的吗?

不是,Spring框架中的单例bean不是线程安全的。

spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。

有状态就是有数据存储功能。 无状态就是不会保存数据。

3.9、 Spring如何处理线程并发问题?

在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。还可以用 scope="prototype",声明bean,每一次都创建新的bean

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

3.10. 解释Spring框架中bean的生命周期

在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。

bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。正如你所见,在bean准备就绪之前,bean工厂执行了若干启动步骤。

我们对上图进行详细描述:

Spring对bean进行实例化;

Spring将值和bean的引用注入到bean对应的属性中;

如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;

如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;

如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;

如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;

如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;

如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;

此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;

如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

现在你已经了解了如何创建和加载一个Spring容器。但是一个空的容器并没有太大的价值,在你把东西放进去之前,它里面什么都没有。为了从Spring的DI(依赖注入)中受益,我们必须将应用对象装配进Spring容器中。

3.11. 哪些是重要的bean生命周期方法? 你能重载它们吗?

有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。

bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)

3.12.什么是Spring的内部bean?什么是Spring inner beans?

在Spring框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现,内部bean通常是匿名的,它们的Scope一般是prototype。

3.13 在 Spring中如何注入一个java集合?

Spring提供以下几种集合的配置元素:

类型用于注入一列值,允许有相同的值。

类型用于注入一组值,不允许有相同的值。

类型用于注入一组键值对,键和值都可以为任意类型。

类型用于注入一组键值对,键和值都只能为String类型。

在Spring中,你可以使用依赖注入(Dependency Injection)的方式来注入Java集合。Spring提供了多种方式来实现集合的注入,具体取决于你要注入的集合类型和配置方式。

下面是几种常见的注入Java集合的方式:

List注入:
可以使用<list>元素或@Autowired注解来注入List集合。
XML配置方式:

<bean id="myBean" class="com.example.MyBean">
    <property name="myList">
        <list>
            <value>item1</value>
            <value>item2</value>
            <value>item3</value>
        </list>
    </property>
</bean>

Java配置方式:

@Configuration
public class MyConfig {

    @Bean
    public List<String> myList() {
        return Arrays.asList("item1", "item2", "item3");
    }

    @Bean
    public MyBean myBean() {
        MyBean myBean = new MyBean();
        myBean.setMyList(myList());
        return myBean;
    }
}

Set注入:可以使用<set>元素或@Autowired注解来注入Set集合。

XML配置方式:

<bean id="myBean" class="com.example.MyBean">
    <property name="mySet">
        <set>
            <value>item1</value>
            <value>item2</value>
            <value>item3</value>
        </set>
    </property>
</bean>

Java配置方式:

@Configuration
public class MyConfig {

    @Bean
    public Set<String> mySet() {
        Set<String> set = new HashSet<>();
        set.add("item1");
        set.add("item2");
        set.add("item3");
        return set;
    }

    @Bean
    public MyBean myBean() {
        MyBean myBean = new MyBean();
        myBean.setMySet(mySet());
        return myBean;
    }
}

Map注入:
可以使用<map>元素或@Autowired注解来注入Map集合。

XML配置方式:

<bean id="myBean" class="com.example.MyBean">
    <property name="myMap">
        <map>
            <entry key="key1" value="value1" />
            <entry key="key2" value="value2" />
            <entry key="key3" value="value3" />
        </map>
    </property>
</bean>

Java配置方式:

@Configuration
public class MyConfig {

    @Bean
    public Map<String, String> myMap() {
        Map<String, String> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        return map;
    }

    @Bean
    public MyBean myBean() {
        MyBean myBean = new MyBean();
        myBean.setMyMap(myMap());
        return myBean;
    }
}

以上示例中,MyBean类具有myListmySetmyMap属性,并通过依赖注入的方式将集合注入到这些属性中。

你可以根据需要选择适合的方式来注入Java集合,无论是使用XML配置还是Java配置,Spring都提供了灵活的方式来实现集合的注入。

3.14 什么是bean装配?

装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。

3.15 什么是bean的自动装配?

在Spring框架中,在配置文件中设定bean的依赖关系是一个很好的机制,Spring 容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过Bean工厂自动处理bean之间的协作。这意味着 Spring可以通过向Bean Factory中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的bean上。

3.16  解释不同方式的自动装配,spring 自动装配 bean 有哪些方式?

在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。

在Spring框架xml配置中共有5种自动装配:

no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。

byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。

byType:通过参数的数据类型进行自动装配。

constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。

autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

3.17  使用@Autowired注解自动装配的过程是怎样的?

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,。

在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据; 如果查询的结果不止一个,那么@Autowired会根据名称来查找; 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

3.18  自动装配有哪些局限性?

自动装配的局限性是:

重写:你仍需用 和 配置来定义依赖,意味着总要重写自动装配。

基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。

模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

3.19  你可以在Spring中注入一个null 和一个空字符串吗?

可以。

4、Spring注解

4.1. 什么是基于Java的Spring注解配置? 给一些注解的例子

基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。

@Configuration
public class StudentConfig {
    @Bean
    public StudentBean myStudent() {
        return new StudentBean();
    }

4.2. 怎样开启注解装配?

注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置元素。

4.3 . @Component, @Controller, @Repository, @Service 有何区别?

pring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:@Repository、@Service 和 @Controller。
在目前的 Spring 版本中,这 3 个注释和 @Component 是等效的,如果 Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用上述注解对分层中的类进行注释。

@Service用于标注业务层组件

@Controller用于标注控制层组件(如struts中的action)

@Repository用于标注数据访问组件,即DAO组件

@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

@Service 
public class VentorServiceImpl implements iVentorService {    
} 
@Repository 
public class VentorDaoImpl implements iVentorDao {  
} 

在一个稍大的项目中,如果组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找以及维护起来也不太方便。 
Spring2.5为我们引入了组件自动扫描机制,他在类路径下寻找标注了上述注解的类,并把这些类纳入进spring容器中管理。
它的作用和在xml文件中使用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:context="http://www.springframework.org/schema/context" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-2.5.xsd">  
    <context:annotation-config /> 
    <context:component-scan base-package=”com.eric.spring”>    
</beans>  

annotation-config是对标记了 Spring's @Required、@Autowired、JSR250's @PostConstruct、@PreDestroy、@Resource、JAX-WS's @WebServiceRef、EJB3's @EJB、JPA's @PersistenceContext、@PersistenceUnit等注解的类进行对应的操作使注解生效。
base-package为需要扫描的包(含所有子包),负责扫描那些类有注解。

getBean的默认名称是类名(头字母小写),如果想自定义,可以@Service(“aaaaa”)这样来指定。
这种bean默认是“singleton”的,如果想改变,可以使用@Scope(“prototype”)来改变。

可以使用以下方式指定初始化方法和销毁方法:

@PostConstruct 
public void init() {  
}  
@PreDestroy 
public void destory() {  
} 

注入方式:
@Autowired
@Autowired顾名思义,就是自动装配,其作用是为了消除代码Java代码里面的getter/setter与bean属性中的property。

注入时不要new 这个注入的类,因为spring会自动注入,如果手动再new的话会出现错误,然后属性加上@Autowired后不需要getter()和setter()方法

当然,getter看个人需求,如果私有属性需要对外提供的话,应当予以保留。

@Autowired接口注入
上面的比较简单,我们只是简单注入一个Java类,那么如果有一个接口,有多个实现,Bean里引用的是接口名,又该怎么做呢?比如有一个Car接口:

public interface Car
{
    public String carName();
}

两个实现类BMW和Benz:

@Service
public class BMW implements Car
{
    public String carName()
    {
        return "BMW car";
    }
}
@Service
public class Benz implements Car
{
    public String carName()
    {
        return "Benz car";
    }
}

写一个CarFactory,引用Car:

@Service
public class CarFactory
{
    @Autowired
    private Car car;
    public String toString()
    {
        return car.carName();
    }
}

不用说,一定是报错的,Car接口有两个实现类,Spring并不知道应当引用哪个实现类。这种情况通常有两个解决办法:

1)、删除其中一个实现类,Spring会自动去base-package下寻找Car接口的实现类,发现Car接口只有一个实现类,便会直接引用这个实现类

2)、实现类就是有多个该怎么办?此时可以使用@Qualifier注解:当接口存在两个实现类的时候必须使用@Qualifier指定注入哪个实现类

@Service
public class CarFactory
{
    @Autowired
    @Qualifier("BMW")
    private Car car;
    public String toString()
    {
        return car.carName();
    }
}

注意@Qualifier注解括号里面的应当是Car接口实现类的类名,我之前试的时候一直以为是bean的名字,所以写了"bMW",结果一直报错。

@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解

共同点

两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。

@Resource

@Service
public class Zoo
{
    @Resource(name = "tiger")
    private Tiger tiger;
 
    @Resource(type = Monkey.class)
    private Monkey monkey;
 
    public String toString()
    {
        return tiger + "\n" + monkey;
    }
}

这是详细一些的用法,说一下@Resource的装配顺序:

@Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配

指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错

然后,区分一下@Autowired和@Resource两个注解的区别:

@Autowired默认按照byType方式进行bean匹配,

@Resource默认按照byName方式进行bean匹配

@Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了

Spring属于第三方的,J2EE是Java自己的东西,因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。

4.4 . @Required 注解有什么作用

这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。示例:

public class Employee {
    private String name;
    @Required
    public void setName(String name){
        this.name=name;
    }
    public string getName(){
        return name;
    }
}

4.5 @Autowired 注解有什么作用

@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。

public class Employee {
    private String name;
    @Autowired
    public void setName(String name) {
        this.name=name;
    }
    public string getName(){
        return name;
    }
}

4.6 @Autowired和@Resource之间的区别

@Autowired可用于:构造函数、成员变量、Setter方法

@Autowired和@Resource之间的区别

@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。 @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

4.7 @Qualifier 注解有什么作用

当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,您可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。

4.8 @RequestMapping 注解有什么用?

@RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类/方法。此注释可应用于两个级别:

类级别:映射请求的 URL 方法级别:映射 URL 以及 HTTP 请求方法

5、Spring数据访问

5.1、 解释对象/关系映射集成模块

Spring 通过提供ORM模块,支持我们在直接JDBC之上使用一个对象/关系映射映射(ORM)工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO和 iBATIS,JPA,TopLink,JDO,OJB 。Spring的事务管理同样支持以上所有ORM框架及JDBC。

5.2、  在Spring框架中如何更有效地使用JDBC?

使用Spring JDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate

5.3、 解释JDBC抽象和DAO模块

通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。

5.4、  spring DAO 有什么用?

Spring DAO(数据访问对象) 使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易以一种统一的方式工作。这使得用户容易在持久性技术之间切换。它还允许您在编写代码时,无需考虑捕获每种技术不同的异常。

5.5、 spring JDBC API 中存在哪些类?

JdbcTemplate SimpleJdbcTemplate NamedParameterJdbcTemplate SimpleJdbcInsert SimpleJdbcCall

5.6、JdbcTemplate是什么

JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。

5.6.1、JdbcTemplate(概念和准备)

1 、什么是 JdbcTemplate(Template译为模板)
( 1 ) Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
 
2 、准备工作
( 1 )引入相关 jar 包

( 2 )在 spring 配置文件配置数据库连接池

com.alibaba.druid.pool.DruidDataSource和com.mysql.jdbc.Driver 都是引入的jar包内的类,不需要自己创建

( 3 )配置 JdbcTemplate 对象,注入 DataSource(即数据库、数据源)
JdbcTemplate是jar包里的类,不是自己创建的。
JdbcTemplate中有个属性叫:dataSource,源码中已经为其设置了set方法,所以用的是set方法注入属性
JdbcTemplate的属性dataSource注入的正是上面数据库连接池对象,等于说把JdbcTemplate对象和数据库连接起来了
 

(4 )使用注解来创建对象和注入属性,所以需要开启组件扫描

完整的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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
 
    <!-- 数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql:///user_db" /><!--对应SQLyog里的数据库-->
        <property name="username" value="root" />            <!-- 用户名 -->
        <property name="password" value="4.233928" />        <!-- 密码 -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>
 
    <!-- JdbcTemplate对象 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入dataSource属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
 
    <!-- 组件扫描 -->
    <context:component-scan base-package="JDBC"></context:component-scan>
 
</beans>

5.6.2、JdbcTemplate 操作数据库(添加)

需求:在表Book中添加一行数据(下图是已经添加完的)

对应数据库创建实体类(三个属性对应表Book的三个参数)

package JDBC;
 
public class Book {
 
    private String userid;
    private String username;
    private String ustatus;
 
    public String getUserid() {
        return userid;
    }
 
    public String getUsername() {
        return username;
    }
 
    public String getUstatus() {
        return ustatus;
    }
 
    public void setUserid(String userid) {
        this.userid = userid;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public void setUstatus(String ustatus) {
        this.ustatus = ustatus;
    }
}

接口BookDao,定义增删查改方法

package JDBC;
 
public interface BookDao {
    //添加(增)
    public void add(Book book);
}

重写add(Book book)方法,向数据库的表中添加数据

JdbcTemplate是jar包org.springframework.jdbc.core中的,添加注解@Autowired后,已经自动注入到JdbcTemplate jdbcTemplate中了。

package JDBC;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
 
@Repository
public class BookDaoImp implements BookDao{
 
    //按类型、自动注入了JdbcTemplate对象(jdbcTemplate即已经是有实体的对象了)
    //JdbcTemplate对象对象里有增删查改的方法,用来操作数据库
    @Autowired
    private JdbcTemplate jdbcTemplate;
 
    @Override
    public void add(Book book) {
 
        //创建添加的sql语句
        String addsql = "insert into Book values(?,?,?)";
        //调用jdbcTemplate.update实现添加,book.getUserid()、book.getUsername()、book.getUstatus()对应values(?,?,?)里的三个问号
        Object[] args = {book.getUserid(), book.getUsername(), book.getUstatus()};
        int update = jdbcTemplate.update(addsql,args);
        //返回值update代表添加了几行
        System.out.println(update);//1
    }
}

BookService类4、BookService类5

package JDBC;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
@Service
public class BookService {
    
    //BookDaoImp类上添加了注解@Repository,即已经生成了他的对象
    //按类型、自动注入了BookDaoImp的对象
    @Autowired
    private BookDao bookDao;
 
    public void addBook(Book book){
        bookDao.add(book);
    }
}

测试 :获取BookService类的对象bookService,调用addBook(book)。

package JDBC;
 
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class JDBCTest {
 
    @Test
    public void test(){
 
        ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
 
        Book book = new Book();
        book.setUserid("1");
        book.setUsername("java");
        book.setUstatus("active");
 
        bookService.addBook(book);
 
    }
}

最终效果:

5.6.3、JdbcTemplate 操作数据库(删、改)——只需修改以下几个文件

package JDBC;
 
public interface BookDao {
    //添加(增)
    public void add(Book book);
 
 
    //修改
    public void update(Book book);
 
    //删除(根据id删除)
    public void delete(String id);
    
}
package JDBC;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
 
@Repository
public class BookDaoImp implements BookDao{
 
    //按类型、自动注入了JdbcTemplate对象
    @Autowired
    private JdbcTemplate jdbcTemplate;
 
    @Override
    public void add(Book book) {
 
        //创建添加的sql语句
        String addsql = "insert into Book values(?,?,?)";
        //调用jdbcTemplate.update实现添加,book.getUserid()、book.getUsername()、book.getUstatus()对应values(?,?,?)里的三个问号
        Object[] args = {book.getUserid(), book.getUsername(), book.getUstatus()};
        int update = jdbcTemplate.update(addsql,args);
        //返回值update代表添加了几行
        System.out.println(update);
    }
 
    @Override
    public void update(Book book) {
        //根据id修改username ustatus
        String updatesql = "update Book set username=?,ustatus=?where user_id=?";
        Object[] args = {book.getUsername(), book.getUstatus(),book.getUserid(),};//注意参数顺序
        int update = jdbcTemplate.update(updatesql,args);
    }
 
    @Override
    public void delete(String id) {
        //根据user_id删除
        String deletesql = "delete from Book where user_id=?";
        int update = jdbcTemplate.update(deletesql,id);
    }
}
package JDBC;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
@Service
public class BookService {
 
    //按类型、自动注入了BookDaoImp的对象
    @Autowired
    private BookDao bookDao;
 
    public void addBook(Book book){
        bookDao.add(book);
    }
 
 
    public void update(Book book){
        bookDao.update(book);
    }
 
    public void delete(String id){
        bookDao.delete(id);
    }
 
}

5.6.4、JdbcTemplate 查询操作

查询返回的是某个值

查询返回的是某个对象

查询返回的是某个集合

批量操作

5.7. 使用Spring通过什么方式访问Hibernate?

5.7.1.使用Spring的HibernateTemplate:

Spring提供了HibernateTemplate类,它简化了Hibernate的使用。我们可以将HibernateTemplate注入到Spring管理的bean中,然后使用它执行CRUD操作。这种方法的优点是简单明了,但它已经在Spring 5中被弃用,因此不再是最佳选择。

@Autowired
private HibernateTemplate hibernateTemplate;

5.7.2.使用Spring的HibernateDaoSupport:

HibernateDaoSupport是一个Spring支持的基类,它提供了与Hibernate的集成。我们可以继承它,并注入SessionFactory来获取Hibernate会话。这种方法较为灵活,但需要继承特定的基类。

public class MyDao extends HibernateDaoSupport {
    @Autowired
    public void setMySessionFactory(SessionFactory sessionFactory) {
        setSessionFactory(sessionFactory);
    }
}

5.7.3.使用Spring的JpaTransactionManager:

  如果我们正在使用JPA (Java Persistence API) 与Hibernate,可以配置Spring的JpaTransactionManager来处理事务。这种方法使得事务管理更容易,但需要确保正确配置JPA

5.7.4.使用Spring的注解驱动方式:

  使用Spring的注解来管理Hibernate事务和会话。我们可以在类和方法级别使用@Transactional注解来标记事务边界,以及使用@Autowired注解注入Hibernate的SessionFactory或EntityManager。

@Repository
@Transactional
public class MyRepository {
    @Autowired
    private SessionFactory sessionFactory;
}

5.7.5.使用Spring Data JPA:

  如果我们的应用程序使用JPA作为持久化技术,Spring Data JPA提供了更高级的方法来简化数据访问。通过定义接口并继承JpaRepository或其他相关接口,Spring Data JPA会自动生成查询方法和事务管理。

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

  这些是一些使用Spring访问Hibernate的方法。选择哪种方法取决于我们的项目需求和个人偏好,但通常来说,Spring Data JPA是一个强大且简化了数据访问的工具,因此在现代Spring应用程序中越来越受欢迎。

5.8 如何通过HibernateDaoSupport将Spring和Hibernate结合起来?

用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步:配置the Hibernate SessionFactory 继承HibernateDaoSupport实现一个DAO 在AOP支持的事务中装配

5.9、Spring支持的事务管理类型, spring 事务实现方式有哪些?

Spring支持两种类型的事务管理:

编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。

声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

5.10、Spring事务的实现方式和实现原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

5.11 说一下Spring的事务传播行为

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

5.12. 说一下 spring 的事务隔离?

spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:

ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么; ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);

ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;

ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;

ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。 脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。

不可重复读 :是指在一个事务内,多次读同一数据。

幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。

5.13、Spring框架的事务管理有哪些优点?

Spring事务是指在Spring框架中对于数据库操作的一种支持,它通过对一组数据库操作进行整体控制来保证数据的一致性和完整性。Spring事务可以保证在一组数据库操作执行时,要么所有操作都执行成功,要么所有操作都回滚到之前的状态,从而避免了数据不一致的情况。

5.13.1、Spring事务实现方式

Spring事务可以通过编程式事务和声明式事务两种方式来实现。编程式事务需要在代码中手动控制事务的开始、提交和回滚等操作,而声明式事务则是通过在配置文件中声明事务的切入点和通知等信息来自动控制事务的行为。

5.13.2、Spring编程式事务

Spring编程式事务需要在代码中获取事务管理器,并通过该事务管理器获取事务对象,然后使用该事务对象来控制事务的开始、提交和回滚等操作。

在上面的代码中,首先通过transactionManager.getTransaction方法获取事务对象status,然后在try块中执行转账操作,最后通过transactionManager.commit(status)提交事务。如果在转账操作中发生了异常,则会通过transactionManager.rollback(status)回滚事务。

编程式事务的优点是灵活性高,可以根据具体的业务需求来灵活控制事务的行为。不过缺点是代码冗长,可读性差,而且容易出现错误。

5.13.3、Spring声明式事务

Spring声明式事务需要在配置文件中声明事务管理器、事务通知等元素,然后在需要使用事务的方法上添加事务切面的注解即可。

在上面的配置文件中,首先声明了事务管理器transactionManager,然后定义了事务通知txAdvice,该通知会在transferMoney方法执行时进行事务管理。最后通过aop:configaop:advisor来将txAdvice应用于transferPointcut定义的切入点上。

5.13.4、声明式事务注解方式

Spring声明式事务也可以通过注解的方式来实现。具体来说,可以在需要使用事务的方法上添加@Transactional注解,并通过该注解的属性来指定事务的传播机制、隔离级别、超时时间等信息。

在上面的代码中,通过@Transactional注解来声明了事务的传播机制为Propagation.REQUIRED,隔离级别为Isolation.DEFAULT,超时时间为3600秒。在方法执行时,Spring会根据注解中的信息自动管理事务的行为。

注解式声明事务的优点是代码简洁、可读性好,灵活性和可用性高,容易维护和调试。但是它也有一些缺点,例如注解的使用可能会导致代码分散,而且无法通过配置文件来管理事务,而且可能会引入一些意想不到的问题。

在使用Spring事务时,需要根据具体的业务需求来选择合适的实现方式,并注意事务的传播机制、隔离级别、超时时间等问题,以保证数据的一致性和完整性。

5.13.5、@Transactional注解的常用参数包括:

value:该属性可以用来指定事务管理器的名称,如果只有一个事务管理器,则可以省略该属性。

propagation:该属性用于指定事务的传播机制,包括REQUIREDSUPPORTSMANDATORYREQUIRES_NEWNOT_SUPPORTEDNEVERNESTED共7种。

isolation:该属性用于指定事务的隔离级别,包括DEFAULTREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE共5种。

timeout:该属性用于指定事务的超时时间,单位为秒,默认值为-1,表示没有超时限制。

readOnly:该属性用于指定事务是否为只读事务,默认值为false,表示事务可读可写。

rollbackFor:该属性用于指定事务回滚的条件,如果出现指定的异常类型,则事务会回滚。

noRollbackFor:该属性用于指定事务不回滚的条件,如果出现指定的异常类型,则事务不会回滚。

除了上述常用参数外,@Transactional注解还有其他一些参数,例如transactionManagerrollbackForClassNamenoRollbackForClassNamevalue等,具体用法可以参考Spring官方文档。

在使用@Transactional注解时,需要根据具体的业务需求来选择合适的参数,以保证事务的正确性和可靠性。

5.13.6、事务注解失效情况

对于Java开发的同学,相信对于Spring的事务在熟悉不过了。但是相信各位小伙伴们在工作中一定有类似于这样的需要:需要同时写入多张表的数据,为了保证操作的原子性(也就是所谓的要么同时成功,要么同时失败),避免产生数据不一致的情况,我们都会使用Spring事务,但是对于Spring事务相信大家也遇到过比较诡异的问题,会出现那种事务失效,或者就是不满足自己的业务场景,其实归根结底还是我们针对于Spring事务的某些特殊场景掌握的不够牢靠。接下来我总结了一下关于Spring事务在某些情况下失效的场景,不出意外的话相信你也遇到过。

Spring事务失效的12种场景总结图

1).访问权限问题

所谓的访问权限问题也就是开发中再熟悉不过的privatedefaultprotectedpublic,它们的访问权限从左到右,依次变大。如果我们在开发过程中国呢,把某些事务方法定义了错误的访问权限,就会导致事务功能出现问题,甚至失效。例如:

@Service
public class UserService {
 @Transactionsl
 private void add(User user){
  saveUser(user);
  updateUser(user);
 }
}

上面代码中我们可以看到对于方法add的访问修饰符被定义成了private,这样会导致事务失效,原因是Spring 要求被代理的方法必须是 **public** 的。简单粗暴来看源码是怎么搞的。如下:


 /**
  * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
  * {@link #getTransactionAttribute} is effectively a caching decorator for this method.
  * <p>As of 4.1.8, this method can be overridden.
  * @since 4.1.8
  * @see #getTransactionAttribute
  */
 @Nullable
 protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
  // Don't allow non-public methods, as configured.
  if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
   return null;
  }

  // The method may be on an interface, but we need attributes from the target class.
  // If the target class is null, the method will be unchanged.
  Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

  // First try is the method in the target class.
  TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
  if (txAttr != null) {
   return txAttr;
  }

  // Second try is the transaction attribute on the target class.
  txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
  if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
   return txAttr;
  }

  if (specificMethod != method) {
   // Fallback is to look at the original method.
   txAttr = findTransactionAttribute(method);
   if (txAttr != null) {
    return txAttr;
   }
   // Last fallback is the class of the original method.
   txAttr = findTransactionAttribute(method.getDeclaringClass());
   if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
    return txAttr;
   }
  }

  return null;
 }

从上述源码中可以看到AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有一个判断,如果方法的修饰符不是public的话,则返回null,并且不支持事务。

也就是说如果我们自定义的事务方法(即目标方法)它的访问权限不是public,而是privatedefalutprotected修饰符的话,Spring都不会提供事务。

2).方法使用final修饰

在某些场景我们可能需要使用final修饰方法,为了不让子类重写等原因,但是针对普通方法而言这是没有任何问题的,但是针对需要加事务的方法则会导致事务失效。如下代码:

@Service
public class UserService {
 @Transactionsl
 private final void add(User user){
  saveUser(user);
  updateUser(user);
 }
}

上述代码中的add方法被final修饰了,从而导致了事务失效。具体原因是什么的。这就要从Spring的源码开始说起了。相比应该是都知道Spring事务的底层其实是使用了AOP,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但是某个方法被final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能

注意:如果某个方法是static修饰的,同样无法通过动态代理,变成事务方法。

3).方法内部调用

在某些场景下,我们需要在某个service类中的某个方法中调用另外一个事务方法。比如:

@Service
public class UserService {
 @Autowired
 private UserMapper usermapper;

 public void add(User user){
  userMappper.insertUser(user);
  updateStatus(user);
 }
}

@Transactional
public void updateStatus(User user){
 doSomeThing();
}

从上面的方法中我们可以看到add()方法中直接调用了事务方法updateStatus();从前面的介绍可以直达,updateStatus()方法拥有事务的能力是因为Spring AOP生成了代理对象,但是这种方法直接调用了this对象的方法,所以updateStatus()方法不会生成事务。

由此可见,在同一类中的方法直接调用,会导致事务失效。

那么我们如何解决在同一方法中调用自己类中的另外一个方法呢?方案如下,这个方法相对简单就是将同一类中调用与被调用的两个方法拆分为两个Service。代码如下:

@Service
public class ServiceA {
 @Autowired
 private ServiceB serviceB;

 public void save(User user){
  queryData1();
  queryData2();
  serviceB.doSave(user);
 }
}

@Service
public class ServiceB{
 @Transactional(rollbackFor = Exception.class)
 public void doSave(User user) {
  addData1(user);
  updateData2(user):
 }
}

在该Service中注入自己,如果不想加一个新的类,其实也可以通过在该类中注入自己也可解决事务失效的问题。代码如下:

@Service
public class ServiceA(){

 @Autowired
 private ServiceA serviceA;

 public void save(User user){
  queryData1();
  queryData2();
  serviceA.doSave(user);
 }

 @Transactional(rollbackFor = Excetion.class)
 public void doSave(User user){
  addData1();
  updateData2(user);
 }

}

可能有人看到这里便会有这样一个疑问,这种做法不会导致循环依赖的问题吗:答案是:不会。在该Service类中使用AopContent.currentProxy()获取对象。虽然上述的方法2也是解决了该问题,但是代码看起来晦涩难懂。接下来我们可以通过AopContent来获取代理对象从而实现相同的功能,代码如下:

@Service
public class ServiceA{

 public void save(User user){
  queryData1();
  queryData2();
  ( (ServiceA)AopContent.currentProxy() ).doSave(user);
 }


 @Transactional(rollbackFor = Excetion.class)
 public void doSave(User user){
  addData1(user);
  updateData2(user);
 }
}

未被Spring管理

在我们市场开发中,还有一个细节很容易被忽略。就是如果需要使用Spring事务,是有一个前置条件,那就是对象需要交给Spring进行管理,需要创建bean实例。

通常情况下,我们通过@Contrlller @Service @Component @Repository等注解,实现将bean实例化喝依赖注入的功能。假设某个时候你再Service上没有添加@Service注解,比如

//@Service
public class UserService{

 @Transactional
 public void add(User user){
  saveData(user);
  updateData(user);
 }
}

上述代码的UserService类没有添加@Service注解,那么该类就不会交给Spring进行统一管理,同时它的add()方法也不会生成事务。

5)多线程调用

在实际的应用开发中。我们通常使用多线程的场景还是很多的。如果Spring事务用在多线程场景下。同样会有问题。代码如下:

@Slf4j
@Service
public class UserService{
 @Autowired
 private UserMapper userMapper;
 @Autowired
 private RoleService roleService;

 @Transactional
 public void add(User user) throws Exception{
  userMapper.insertUser(user);
  new Thread( ()-> {
   roleService.doOtherThing();
  }).start();
 }
}


@Service
public class RoleService{

 @Transactional
 public void doOtherThing(){
  System.out.println("保存roles数据");
 }

}

从上面的例子,我们可以看到事务方法add()中,调用了事务方法doOtherThing(),但是事务方法doOtherThing()是在另外一个线程中调用的。这样会导致两个方法不在一个线程中。获取的数据库连接也就不一致,从而是两个不同的事务。如果doOtherThing()方法中抛出了异常,add()方法是不可能回滚的。

如果看过Spring源码的小伙伴应该知道Spring的事务是通过数据库的连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。

private static final ThreadLocal<Map<Object,Object>> resources = new NamedThreadLocal<>("Transactional resources");

我们说的同一个事务,其实指同一个数据库连接,只有拥有同一个数据库连接才能同事提交和回滚。如果在不同的线程中,拿到的数据库连接肯定是不一样的。所以事务也是不同的。

6).表不支持事务

众所周知,在MySQL 5.x之前,默认的数据库引擎是myisam。它的优缺点就不说了:索引文件和数据文件是分开存储的,对于查多写少的表操作,性能要比InnoDB要更好。在一些老的项目中用它的有很多。创建一个MyIsam引擎的表:

CREATE TABLE `category` (
 `id` bigint NOT NULL AUTO_INCREMENT,
 `one_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
 `two_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
 `three_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
 `four_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

虽然MyIsam引擎好用,但是有一个致命的缺点,那就是不支持事务。如果是单表操作还好,不会出现太大的问题,但是如果是跨表操作,由于其不支持事务,数据极有可能出现不完整的情况。此外MyIsam还不支持 行锁外键。所以时间的业务场景中,MyIsam的使用场景不多,在MySQL 5.x之后,MyIsam已经逐渐的退出了历史舞台,取而代之的是引擎InnoDB,所以在实际的开发中如果事务没有生效,有可能就是因为你的表的引擎不支持事务。

7).未开启事务

有些时候,事务没有生效的根本原因是没有开启事务。如果你使用的是Spring Boot项目,那么很幸运,因为Spring Boot已经通过DataSoureTransactionManagerAutoConfiguration类,默认开启了事务,你只需要配置spring.datasource的相关参数即可。

如果是Spring项目则需要一下配置信息:

<!-- 配置事务管理器 --> 
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 
<tx:advice id="advice" transaction-manager="transactionManager"> 
    <tx:attributes> 
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes> 
</tx:advice> 
<!-- 用切点把事务切进去 --> 
<aop:config> 
    <aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/> 
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
</aop:config> 

注意:如果在pointcut标签中的切入点匹配规则,配错了的话,有些类的事务也不会生效。以上说的都是单纯的事务没有生效。但是在实际的开发过程中还存在另外一种情况,就是事务生效了,但是没有回滚,或者说事务执行没有达到预期

Spring中事务未生效的场景之事务未回滚

Spring的事务不回滚,错误的传播特性,说到事务的传播特性,首先应该知道事务的传播特性有哪些:

8)如果在编写代码时将事务的传播特性编写出错。比如:

@Service
public class UserService {

 @Transactional(propagation = Propagation.NEVER)
 public void doSave(User user){
  saveData(user);
  updateData(user);
 }
}

我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。

目前只有三种事务传播特性才会新建事务REQUIRED REQUIRED_NEW NESTED

9).自己吞了异常

在开发过程中,有可能我们在事务中使用了try{}catch()了异常。比如:


@Slf4j
@Service
public class UserService {

 @Transactional
 public void add(User user){
  try{
   saveData(user);
   updateData(user);
  } catch (Exception e){
   log.error(e.getMessage(),e);
  }
 }

}

10)手动抛出了别的异常

即使开发者在编写过程中,没有手动抛出异常;但是如果出现的异常不正确,Spring事务也不会回滚。

@Slf4j
@Service
public class UserService{

 @Transactional
 public void add(User user) throws Exception {
  try {
   saveData(user);
   updateData(user);
  } catch(Exception e) {
   log.error(e.getMessage(), e);
   throw new Exception(e);
  }
 }

}

上述这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。因为Spring事务,默认情况下只会回滚RunTimeException,和Error(错误),对于普通的Exception(非运行时异常),它是不会回滚的。

11)自定义了回滚异常

在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数,来完成这个功能。但如果这个参数的值设置错了,就会引出一些莫名其妙的问题,例如:

@Slf4j
@Service
public class UserService {
    
    @Transactional(rollbackFor = BusinessException.class)
    public void add(User user) throws Exception {
       saveData(user);
       updateData(user);
    }
}

如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。

即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。

rollbackFor默认值为UncheckedException,包括了RuntimeException和Error. 当我们直接使用@Transactional不指定rollbackFor时,Exception及其子类都不会触发回滚。

所以,建议一般情况下,将该参数设置成:Exception或Throwable。

12)嵌套事务回滚多了

public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel user) throws Exception {
        userMapper.insertUser(user);
        roleService.doOtherThing();
    }
}

@Service
public class RoleService {

    @Transactional(propagation = Propagation.NESTED)
    public void doOtherThing() {
        System.out.println("保存role表数据");
    }
}

这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing()方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。但事实是,insertUser也回滚了。

这是为什么呢?

因为doOtherThing()方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。

如何才能只回滚保存点呢?

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(User user) throws Exception {

        userMapper.insertUser(userModel);
        try {
            roleService.doOtherThing();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。

好了以上就是整理的Spring事务在开发过程中会出现的诡异的情况。

@Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务的敌方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎、消息补偿、统计修正等。

在使用Spring事务时,需要注意对于事务的传播机制和隔离级别的设置,以及对于事务的异常处理等问题。正确地使用Spring事务可以提高系统的数据一致性和可靠性。

5.13.7、什么是Spring事务传播机制

Spring事务传播机制是指在多个事务操作发生时,如何管理这些操作之间的事务关系。Spring事务传播机制可以通过Propagation枚举类中的不同值来指定,共包括七种不同的传播行为。具体来说,Spring事务传播机制包括以下七种:

REQUIRED:如果当前没有事务,则创建一个新的事务;如果当前已经存在事务,则加入该事务。这是默认的传播行为。

SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。

MANDATORY:必须在一个已存在的事务中执行,否则就抛出TransactionRequiredException异常。

REQUIRES_NEW:创建一个新的事务,并在该事务中执行;如果当前存在事务,则将当前事务挂起。

NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。

NEVER:以非事务方式执行操作,如果当前存在事务,则抛出IllegalTransactionStateException异常。

NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务。

在使用Spring事务传播机制时,需要根据具体的业务需求来选择合适的传播行为,以保证事务的正确性和可靠性。同时,需要注意事务的异常处理等问题,以避免数据不一致或丢失的情况发生。

@Transactional注解中的传播机制

除了在代码中使用编程式事务和声明式事务外,Spring还提供了@Transactional注解来实现事务控制。在使用@Transactional注解时,可以通过propagation属性来指定事务的传播机制,例如:

在上面的代码中,通过propagation属性来指定了事务的传播机制为Propagation.REQUIRED,即如果当前没有事务,则创建一个新的事务;如果当前已经存在事务,则加入该事务。

除了Propagation枚举类中的七种传播行为外,@Transactional注解还可以通过isolationtimeoutreadOnly等属性来指定事务的隔离级别、超时时间和只读事务等信息。

在使用@Transactional注解时,需要根据具体的业务需求来选择合适的传播行为和其他属性,以保证事务的正确性和可靠性。

5.13.8、Spring事务的隔离级别

Spring事务的隔离级别是指多个事务之间的隔离程度,它可以通过设置isolation属性来指定。Spring事务的隔离级别包括以下五种:

DEFAULT:默认的隔离级别,由底层数据库引擎决定。

READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据。该级别会导致“脏读”、“不可重复读”和“幻读”等问题。

READ_COMMITTED:只允许读取已经提交的数据。该级别可以避免“脏读”,但可能会导致“不可重复读”和“幻读”等问题。

REPEATABLE_READ:保证在同一个事务中多次读取同一数据时,该数据的值不会发生变化。该级别可以避免“脏读”和“不可重复读”,但可能会导致“幻读”等问题。

SERIALIZABLE:最高的隔离级别,强制事务串行执行,避免了“脏读”、“不可重复读”和“幻读”等问题。但是该级别会对性能产生较大的影响,因此一般不建议使用。

在选择隔离级别时,需要根据具体的业务需求来选择合适的级别。一般来说,如果不需要在事务中读取未提交的数据,那么可以选择READ_COMMITTED级别;如果需要避免“不可重复读”问题,可以选择REPEATABLE_READ级别;如果需要避免“幻读”问题,可以选择SERIALIZABLE级别。但是需要注意的是,隔离级别越高,事务的并发性越差,因此需要根据具体业务场景来权衡隔离级别和性能。

不可重复读和幻读的区别

不可重复读和幻读都是在并发读写数据时可能出现的问题。

不可重复读指的是在一个事务中多次读取同一数据,但是由于其他事务的修改,导致两次读取的结果不一致。例如,事务A在读取某个数据时,事务B修改了该数据并提交,然后事务A再次读取该数据时,得到了不同的结果。

幻读是指在一个事务中多次读取同一范围内的数据,但是由于其他事务的插入操作,导致两次读取的结果不一致。例如,事务A在读取某个范围内的数据时,事务B插入了一条数据并提交,然后事务A再次读取该范围内的数据时,得到了不同的结果。

不可重复读和幻读的区别在于,不可重复读是在读取同一数据时出现问题,而幻读是在读取同一范围内的数据时出现问题。解决不可重复读的问题可以使用锁机制或者提高事务的隔离级别,而解决幻读的问题可以使用锁机制或者使用更高的隔离级别,例如SERIALIZABLE级别。

5.13.9、rollbackFor属性

在使用@Transactional注解进行声明式事务管理时,可以通过rollbackFor属性来指定哪些异常类型需要回滚事务。需要注意的是,rollbackFor属性指定的异常类型必须是Throwable类型或其子类,并且必须包含在Spring事务抛出的异常类型之内。如果指定的异常类型不正确或不在事务回滚的异常类型之内,可能会导致事务无法正确回滚或者回滚不完整的问题。

如果需要指定多个异常类型,可以使用数组的方式进行指定,例如:

在上面的代码中,指定了SQLExceptionIOException两种异常类型需要回滚事务。默认情况只有RuntimeException和Error会触发事务回滚。

除了rollbackFor属性外,@Transactional注解还有其他一些属性可以用于指定事务的属性和行为,包括propagationisolationtimeoutreadOnly等。需要根据具体的业务需求来选择合适的属性和行为,以保证事务的正确性和可靠性。

6、Spring面向切面编程(AOP)

6.1、 什么是AOP

AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

6.2、Spring AOP and AspectJ AOP 有什么区别?

AOP 有哪些实现方式?

AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

6.3、JDK动态代理和CGLIB动态代理的区别

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码动态地将横切逻辑和业务编织在一起;

接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。

CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

6.4、 如何理解 Spring 中的代理?

 在 Spring 中,代理是一种机制,用于控制对目标对象的访问。Spring框架主要使用代理来实现AOP(面向切面编程)和事务管理。理解Spring中的代理需要了解以下两种代理类型:

6.4.1、 JDK 动态代理

 基于接口的代理,使用 `java.lang.reflect.Proxy` 类和 `InvocationHandler` 接口。当目标对象实现了接口时,Spring会使用JDK动态代理来创建代理对象。代理对象实现了目标接口,并将方法调用转发到 InvocationHandler 的 `invoke` 方法。JDK动态代理要求目标对象实现接口,因此它只能代理接口中定义的方法。

6.4.2、CGLIB(Code Generation Library)代理:

基于类的代理,通过生成目标类的子类来实现代理。当目标对象没有实现接口时,Spring会使用CGLIB来创建代理对象。它会生成一个目标类的子类,并重写其中的方法,实现拦截器的功能。

 CGLIB代理不要求目标对象实现接口,因此可以代理类的方法。

Spring的代理在AOP和事务管理中扮演重要角色:

AOP: 通过代理,Spring能够在目标方法执行前后、异常抛出等时机织入切面逻辑,例如日志记录、性能监控等。

事务管理: Spring使用代理实现声明式事务管理。它在方法执行前后控制事务的开启、提交或回滚,确保数据一致性和完整性。

理解Spring代理意味着理解它是如何在运行时创建代理对象,并在方法调用时将控制权转移到代理对象上的。代理在Spring中是实现AOP和事务管理的关键机制,它提供了灵活而强大的方式来管理和控制应用程序的行为。

6.5、 解释一下Spring AOP里面的几个名词

6.5.1、连接点(Joinpoint)

(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

(3)通知(Advice):在AOP术语中,切面的工作被称为通知。

(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。

(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

(7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:

编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。

类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。

运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。

6.6、Spring在运行时通知对象

通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。

6.7、Spring只支持方法级别的连接点

Spring AOP支持的Aspect切入点指示符(用来指示切入点表达式目的),在Spring AOP中目前只有执行方法这一个连接点(因为Spring基于动态代理,所以Spring只支持方法连接点。这与一些其他的AOP框架是不同的,例如AspectJ和JBoss,除了方法切点,它们还提供了字段和构造器接入点。Spring缺少对字段连接点的支持,无法让我们创建细粒度的通知,例如拦截对象字段的修改。而且它不支持构造器连接点,我们就无法在bean创建时应用通知。但是方法拦截可以满足绝大部分的需求。如果需要方法拦截之外的连接点拦截功能,那么我 们可以利用AspectJ来补充Spring AOP的功能)。

6.7.1、Spring AOP支持的Aspect切入点指示符如下:

1)方法描述匹配:

execution():用于匹配方法执行的连接点;

语法结构(AspectJ切点表达式):execution( 方法修饰符  方法返回值   匹配方法名 (  方法中的形参表 )  方法申明抛出的异常  ),其中红色字体的部分时不能省略的,各部分都支持通配符 “*” 来匹配全部。

2)方法参数匹配:

args():用于匹配当前执行的方法传入的参数为指定类型的执行方法;

@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;

3)当前AOP代理对象类型匹配

this():用于匹配当前AOP代理对象类型的执行方法;

4)目标类匹配:

target():用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,接口也能匹配

@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;

 within():用于匹配指定类的方法执行,接口不能匹配

@within:用于匹配持有指定注解类型内的方法;

例子

Vehicle 接口  VehicleImp 实现类

Main函数调用.

Vehicle v1 = (Vehicle)context.getBean("vehicleimp");

v1.drive();

<bean id="vehicleimp" class="com.bbb.sia4.introduction.VehicleImp"/>

<aop:pointcut id="pointcut-pose" expression="execution(* *..drive()) and this(VehicleImp) "/> 匹配不到

<aop:pointcut id="pointcut-pose" expression="execution(* *..drive()) and this(Vehicle) "/>  匹配到

<aop:pointcut id="pointcut-pose" expression="execution(* *..drive()) and target(VehicleImp) "/> 匹配到

<aop:pointcut id="pointcut-pose" expression="execution(* *..drive()) and target(Vehicle) "/> 匹配到

<aop:pointcut id="pointcut-pose" expression="execution(* *..drive()) and within(VehicleImp) "/> 匹配到

<aop:pointcut id="pointcut-pose" expression="execution(* *..drive()) and within(Vehicle) "/> 匹配不到

<aop:pointcut id="pointcut-pose" expression="execution(* *..drive()) and within(Vehicle+) "/> 匹配到

说明:

1).this(VehicleImp)匹配不到,因为context.getBean()获得的对象v1是个被包装过的代理对象.不是VehicleImp类型. target(VehicleImp)能匹配到,匹配的是被代理的实际(VehicleImp)对象.

2).代理类型和被代理类型都符合接口Vehicle,this(Vehicle)和target(Vehicle)都能匹配到.

3).with和target的目标对象一样是实际被代理的对象(本文中VehicleImp).within不支持继承关系.所以within(Vehicle)没匹配到.可以使用+,within(Vehicle+)匹配.

总结:this是匹配代理后的包装过的对象, target和within匹配被代理原本的对象(vehicleimp).within不匹配继承关系.

5)标有此注解的方法匹配:

 @annotation:用于匹配当前执行方法持有指定注解的方法;

6.7.2、命名及匿名切入点

命名切入点可以被其他切入点引用,而匿名切入点是不可以的。只有@AspectJ支持命名切入点,而Schema风格不支持命名切入点。如下所示,@AspectJ使用如下方式引用命名切入点:

6.7.3、类型匹配语法

首先让我们来了解下AspectJ类型匹配的通配符:

 *:匹配任何类型字符;

 ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。

+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。

示例:

1、java.lang.String    匹配String类型; 
2、java.*.String        匹配java包下的任何“一级子包”下的String类型; 
   如匹配java.lang.String,但不匹配java.lang.ss.String 
3、java..*             匹配java包及任何子包下的任何类型; 
   如匹配java.lang.String、java.lang.annotation.Annotation 
4、java.lang.*ing      匹配任何java.lang包下的以ing结尾的类型; 
5、java.lang.Number+  匹配java.lang包下的任何Number的自类型; 
                   如匹配java.lang.Integer,也匹配java.math.BigInteger

接下来再看一下具体的匹配表达式类型吧

匹配类型:使用如下方式匹配,注解? 类的全限定名字

注解:可选,类型上持有的注解,如@Deprecated;

类的全限定名:必填,可以是任何类全限定名。

匹配方法执行:使用如下方式匹配:注解? 修饰符? 返回值类型 类型声明?方法名(参数列表) 异常列表?

注解:可选,方法上持有的注解,如@Deprecated;

修饰符:可选,如public、protected;

返回值类型:必填,可以是任何类型模式;“*”表示所有类型;

类型声明:可选,可以是任何类型模式;

方法名:必填,可以使用“*”进行模式匹配;

参数列表:“()”表示方法没有任何参数;“(..)”表示匹配接受任意个参数的方法,“(..,java.lang.String)”表示匹配接受java.lang.String类型的参数结束,且其前边可以接受有任意个参数的方法;“(java.lang.String,..)” 表示匹配接受java.lang.String类型的参数开始,且其后边可以接受任意个参数的方法;“(*,java.lang.String)” 表示匹配接受java.lang.String类型的参数结束,且其前边接受有一个任意类型参数的方法;

异常列表:可选,以“throws 异常全限定名列表”声明,异常全限定名列表如有多个以“,”分割,如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException。

匹配Bean名称:可以使用Bean的id或name进行匹配,并且可使用通配符“*”;

6.7.4、组合切入点表达式

AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。在Schema风格下,由于在XML中使用“&&”需要使用转义字符“&amp;&amp;”来代替之,所以很不方便,因此Spring AOP 提供了and、or、not来代替&&、||、!。

切入点使用示例 execution():使用“execution(方法表达式)”匹配方法执行;

模式

描述

public * *(..)

任何公共方法的执行

* cn.javass..IPointcutService.*()

cn.javass包及所有子包下IPointcutService接口中的任何无参方法

* cn.javass..*.*(..)

cn.javass包及所有子包下任何类的任何方法

* cn.javass..IPointcutService.*(*)

cn.javass包及所有子包下IPointcutService接口的任何只有一个参数方法

* (!cn.javass..IPointcutService+).*(..)

非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法

* cn.javass..IPointcutService+.*()

cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法

* cn.javass..IPointcut*.test*(java.util.Date)

cn.javass包及所有子包下IPointcut前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的

如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的;

* cn.javass..IPointcut*.test*(..)  throws

IllegalArgumentException, ArrayIndexOutOfBoundsException

cn.javass包及所有子包下IPointcut前缀类型的的任何方法,且抛出IllegalArgumentException和ArrayIndexOutOfBoundsException异常

* (cn.javass..IPointcutService+

&& java.io.Serializable+).*(..)

任何实现了cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的类型的任何方法

@java.lang.Deprecated * *(..)

任何持有@java.lang.Deprecated注解的方法

@java.lang.Deprecated @cn.javass..Secure  * *(..)

任何持有@java.lang.Deprecated和@cn.javass..Secure注解的方法

@(java.lang.Deprecated || cn.javass..Secure) * *(..)

任何持有@java.lang.Deprecated或@ cn.javass..Secure注解的方法

(@cn.javass..Secure  *)  *(..)

任何返回值类型持有@cn.javass..Secure的方法

*  (@cn.javass..Secure *).*(..)

任何定义方法的类型持有@cn.javass..Secure的方法

* *(@cn.javass..Secure (*) , @cn.javass..Secure (*))

任何签名带有两个参数的方法,且这个两个参数都被@ Secure标记了,

如public void test(@Secure String str1,

@Secure String str1);

* *((@ cn.javass..Secure *))或

* *(@ cn.javass..Secure *)

任何带有一个参数的方法,且该参数类型持有@ cn.javass..Secure;

如public void test(Model model);且Model类上持有@Secure注解

* *(

@cn.javass..Secure (@cn.javass..Secure *) ,

@ cn.javass..Secure (@cn.javass..Secure *))

任何带有两个参数的方法,且这两个参数都被@ cn.javass..Secure标记了;且这两个参数的类型上都持有@ cn.javass..Secure;

* *(

java.util.Map<cn.javass..Model, cn.javass..Model>

, ..)

任何带有一个java.util.Map参数的方法,且该参数类型是以< cn.javass..Model, cn.javass..Model >为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型;

如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *(

java.util.HashMap<cn.javass..Model,cn.javass..Model>

, ..)”进行匹配;

而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配

* *(java.util.Collection<@cn.javass..Secure *>)

任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;

如public void test(Collection<Model> collection);Model类型上持有@cn.javass..Secure

* *(java.util.Set<? extends HashMap>)

任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型继承与HashMap;

Spring AOP目前测试不能正常工作

* *(java.util.List<? super HashMap>)

任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型是HashMap的基类型;如public voi test(Map map);

Spring AOP目前测试不能正常工作

* *(*<@cn.javass..Secure *>)

任何带有一个参数的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;

Spring AOP目前测试不能正常工作

within():使用“within(类型表达式)”匹配指定类型内的方法执行;

模式

描述

within(cn.javass..*)

cn.javass包及子包下的任何方法执行

within(cn.javass..IPointcutService+)

cn.javass包或所有子包下IPointcutService类型及子类型的任何方法

within(@cn.javass..Secure *)

持有cn.javass..Secure注解的任何类型的任何方法

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

this():使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符;

模式

描述

this(cn.javass.spring.chapter6.service.IPointcutService)

当前AOP对象实现了 IPointcutService接口的任何方法

this(cn.javass.spring.chapter6.service.IIntroductionService)

当前AOP对象实现了 IIntroductionService接口的任何方法

也可能是引入接口

通知参数

target():使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符;

@args:使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;注解类型也必须是全限定类型名;

@annotation:使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名;

bean():使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring AOP扩展的,在AspectJ中无相应概念;

reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持,如下所示:

 比如我们定义如下切面:

package cn.javass.spring.chapter6.aop; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.Pointcut; 
@Aspect 
public class ReferencePointcutAspect { 
    @Pointcut(value="execution(* *())") 
    public void pointcut() {} 
}

可以通过如下方式引用:

@Before(value = "cn.javass.spring.chapter6.aop.ReferencePointcutAspect.pointcut()") 
public void referencePointcutTest2(JoinPoint jp) {} 

除了可以在@AspectJ风格的切面内引用外,也可以在Schema风格的切面定义内引用,引用方式与@AspectJ完全一样。 

 到此我们切入点表达式语法示例就介绍完了,我们这些示例几乎包含了日常开发中的所有情况,但当然还有更复杂的语法等等,如果以上介绍的不能满足您

6.8、 在Spring AOP 中,关注点和横切关注的区别是什么?

在 spring aop 中 concern 和 cross-cutting concern 的不同之处 关注点(concern)是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。

横切关注点(cross-cutting concern)是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

6.9、Spring通知有哪些类型?

通知类型

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知,它们分别是:

前置通知 (@Before) 。

返回通知 (@AfterReturning) 。

异常通知 (@AfterThrowing) 。

后置通知 (@After)。

环绕通知 (@Around) :(优先级最高)

通知执行顺序

将上面的所有通知类型写入同一个切面中,它的执行顺序为:

Spring版本为5.2.7.RELEASE之前:

正常情况:
@Around 环绕通知前
@Before前置通知
result:5
@Around 环绕通知后
@After后置通知
@AfterReturning返回后通知
 
异常情况:
@Around 环绕通知前
@Before前置通知
@After后置通知
@AfterThrowing异常通知

Spring版本为5.2.7.RELEASE之后:

正常情况:
@Around 环绕通知前
@Before前置通知
result:5
@AfterReturning返回后通知
@After后置通知
@Around 环绕通知后
 
异常情况:
@Around 环绕通知前
@Before前置通知
@AfterThrowing异常通知
@After后置通知

示例

package com.szh.service;
 
/**
 *
 */
public interface SomeService {
 
    String doSome(String name,Integer age);
 
}

由于我们不写xml配置,所以需要在业务接口实现类上添加 @Service 注解,通过注解将这个类交给Spring IOC容器管理。 

package com.szh.service.impl;
 
import com.szh.service.SomeService;
import org.springframework.stereotype.Service;
 
@Service
public class SomeServiceImpl implements SomeService {
 
    @Override
    public String doSome(String name, Integer age) {
        //int a = 10 / 0; //用于测试异常通知
        System.out.println("业务方法doSome(),创建商品的订单");
        return "姓名:" + name + ",年龄:" + age;
    }
 
}

然后是我们的切面类,同上面的业务实现类,也需要使用 @Component 注解将这个类交给Spring IOC容器管理。 

package com.szh.handle;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
 
@Aspect
@Component
public class MyAspect {
 
    @Before(value = "myPointCut()")
    public void myBefore() {
        System.out.println("前置通知,在目标方法之前先执行的....");
    }
 
    @AfterReturning(value = "myPointCut()", returning = "obj")
    public void myAfterReturning(Object obj) {
        System.out.println("后置通知,在目标方法之后再执行的(如有异常,则后置通知不会执行).... 目标方法返回值:" + obj);
    }
 
    @Around(value = "myPointCut()")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知中前置通知的功能....");
        Object obj = joinPoint.proceed();
        System.out.println("环绕通知中后置通知的功能....");
        return obj;
    }
 
    @AfterThrowing(value = "myPointCut()", throwing = "e")
    public void myAfterThrowing(Exception e) {
        System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:" + e.getMessage());
    }
 
    @After(value = "myPointCut()")
    public void myAfter() {
        System.out.println("最终通知,总是会被执行的....");
    }
 
    @Pointcut(value = "execution(* com.szh.service.impl.*.do*(..))")
    private void myPointCut() {
        //无需代码
    }
}

最后再来个配置类,在这个配置类中开启要替代xml配置文件的相关功能。

package com.szh.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
 
/**
 *
 */
@Configuration
//开启包扫描功能,相当于xml中的 <context:component-scan base-package="com.szh"></context:component-scan>
@ComponentScan(basePackages = "com.szh")
//开启aop自动代理功能,相当于xml中的 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy
public class MyConfig {
}

最后是测试类。

package com.szh;
 
import com.szh.config.MyConfig;
import com.szh.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
/**
 *
 */
public class MyTest {
 
    @Test
    public void testAnnotationAop() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        SomeService service = (SomeService) context.getBean("someServiceImpl");
        String info = service.doSome("张起灵", 20);
        System.out.println(info);
    }
}

无异常的执行结果

有异常的执行结果

6.10、什么是切面 Aspect?

aspect 由 pointcount 和 advice 组成,切面是通知和切点的结合。 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中.

AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:

如何通过 pointcut 和 advice 定位到特定的 joinpoint 上 如何在 advice 中编写切面代码. 可以简单地认为, 使用 @Aspect 注解的类就是切面。

6.11、 解释基于XML Schema方式的切面实现

在这种情况下,切面由常规类以及基于XML的配置实现。

创建一个类去实现这个加减乘除法的接口

package com.spring.aop;
 
import org.springframework.stereotype.Component;
 
/**
 * @Author zhang yh
 * @Date 2023/3/24 9:49
 * @Description
 * @Version 1.0
 */
@Component
public class CacluatetTest implements CacluateDao{
 
    public int add(int num1, int num2) {
        return num1 + num2;
    }
 
    public int subtract(int num1, int num2) {
        return num1 - num2;
    }
 
    public int multiply(int num1, int num2) {
        return num1 * num2;
    }
 
    public int divide(int num1, int num2) {
        return num1 / num2;
    }
}

定义一个切面通知

package com.spring.aop;
 
import org.aspectj.lang.JoinPoint;
 
import java.util.Arrays;
import java.util.List;
 
/**
 * @Author zhang yh
 * @Date 2023/3/24 9:56
 * @Description
 * @Version 1.0
 */
 
public class LoggingAspect {
 
 
    /**
     *      方法要求是public访问类型的
     *      方法是无返回值的
    */
    public void addBeforeAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> list = Arrays.asList(joinPoint.getArgs());
        System.out.println("执行在" + methodName + "方法前的日志信息,方法参数是:" + list);
    }
 
}

在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: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/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
 
 
<!-- 使用XML配置实现前置通知-->
    <bean id="cacluatetTest" class="com.spring.aop.CacluatetTest"/>
    <bean id="loggingAspect" class="com.spring.aop.LoggingAspect"/>
        <aop:config proxy-target-class="true">
            <aop:aspect ref="loggingAspect">
                <aop:pointcut id="poiointcutTest" expression="execution(* com.spring.aop.CacluatetTest.*(..))"/>
                <aop:before pointcut-ref="poiointcutTest" method="addBeforeAdvice"/>
            </aop:aspect>
        </aop:config>
 
</beans>

6.12、解释基于注解的切面实现

由于Spring家族的不断膨胀,以前基于xml配置的使用方式使各个开发者头疼不已,维护一个大型系统的配置文件居然成了一笔不小的人力成本,显然,从Spring framework到SpringBoot,从以前的集中一站式应用到现在的微服务应用,技术的发展的趋势是不断的应用拆分和精简,将单个系统的规模控制在一个可控的范围内,而基于注解的配置方式显然要比以前的基于xml的方式精简太多,而且注解通常是与代码放在一起,提升了代码的可读性。

package com.chenrui.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintMethodName {

}

然后我们实现我们的切面逻辑,需要在我们的Class上加上@Aspect注解,用于标注这是一个切面实现,注意也要加上@Component注解,否则不会被Spring识别。

package com.chenrui.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PrintMethodNameAspect {

    @Pointcut("@within(com.chenrui.aspect.PrintMethodName) || @annotation(com.chenrui.aspect.PrintMethodName)")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object process(ProceedingJoinPoint point) throws Throwable {
        System.out.println(point.getSignature().getName());
        return point.proceed();
    }

}

其中

1 @Pointcut("@within(com.chenrui.aspect.PrintMethodName) || @annotation(com.chenrui.aspect.PrintMethodName)")

是我们定义的一个检查点(Pointcut),@within表示的是在PrintMethodName注解所标记的Class内,而@annotation表示的是被PrintMethodName注解标记的方法。因此,不管我们将切面注解标注在Class上还是Method上,我们都可以拦截到。下面我们定义一个简单的实现类。

package com.chenrui.aspect;

import org.springframework.stereotype.Component;

@Component
public class AspectService {

    @PrintMethodName
    public void firstMethod() {
        System.out.println("Into the first method.");
        innerCallMethod();
    }

    @PrintMethodName
    public void secondMethod() {
        System.out.println("Into the second method.");
    }

    @PrintMethodName
    public void innerCallMethod() {
        System.out.println("Into the inner call method.");
    }

}

三个方法firstMethod,secondMethod和innerCallMethod我们都加上了@PrintMethodName注解,现在我们来看一看效果。

package com.chenrui.aspect;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/aspect")
public class AspectTestController {

    @Autowired
    private AspectService aspectService;

    @RequestMapping("/test")
    public Object testPrintMethod() {
        aspectService.firstMethod();
        aspectService.secondMethod();
        return "OK";
    }

}

我们只调用了firstMethod和secondMethod,现在来看看控制台会打印出什么。

firstMethod
Into the first method.
Into the inner call method.
secondMethod
Into the second method.

显然,切面在firstMethod和secondMethod处都起作用了,但是在innerCallMethod处失效了,下一篇文章我将从Spring的实现方式上来说明为什么切面对于innerCallMethod方法会失效。

6.13、有几种不同类型的自动代理?

BeanNameAutoProxyCreator DefaultAdvisorAutoProxyCreator Metadata autoproxying

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吹老师个人app编程教学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值