后端开发知识点汇总(十)Spring篇

文章目录

    • 一、Spring概述
      • 1.1 什么是spring?
      • 1.2 Spring框架的设计目标,设计理念,和核心是什么
      • 1.3 Spring的优缺点是什么?
      • 1.4 Spring由哪些模块组成?【基础】
      • 1.5 Spring 框架中都用到了哪些设计模式?【重要】
      • 1.6 Spring框架中有哪些不同类型的事件【重要】
      • 1.7 Spring 应用程序有哪些不同组件?
    • 二、控制反转(IOC)
      • 2.1 什么是Spring IOC 容器?【重要】
      • 2.2 控制反转(IoC)有什么作用
      • 2.3 IOC的优点是什么?
      • 2.4 Spring IoC 的实现机制【基础】
      • 2.5 Spring 的 IoC支持哪些功能
      • 2.6 BeanFactory 和 ApplicationContext有什么区别?【重要】
      • 2.7 ApplicationContext通常的实现是什么?【重要】
      • 2.8 什么是Spring的依赖注入,几种形式?【重要】
      • 2.9 依赖注入(DI)有什么优势
      • 2.10 构造器依赖注入和 Setter方法注入的区别
      • 2.11 实例化bean的三种方式
    • 三、Spring Beans
      • 3.1 什么是Spring beans
      • 3.2 Spring有几种配置方式
      • 3.3 解释Spring支持的几种bean的作用域【重要】
      • 3.4 Spring框架中的单例bean是线程安全的吗?
      • 3.5 Spring如何处理线程并发问题?
      • 3.6 解释Spring框架中bean的生命周期
      • 3.7 哪些是重要的bean生命周期方法? 你能重载它们吗?【重要】
      • 3.8 什么是Spring的内部bean?
      • 3.9 在 Spring中如何注入一个java集合?【重要】
      • 3.10 什么是bean装配?
      • 3.11 什么是bean的自动装配?【基础】
      • 3.12 spring 自动装配 bean 有哪些方式?【基础】
      • 3.13 使用@Autowired注解自动装配的过程是怎样的?
      • 3.14 自动装配有哪些局限性?
      • 3.15 可以在Spring中注入一个null 和一个空字符串吗?
    • 四、Spring注解
      • 4.1 什么是基于Java的Spring注解配置? 给一些注解的例子
      • 4.2 怎样开启注解装配?
      • 4.3 @Component, @Controller, @Repository, @Service 有何区别?【重要】
      • 4.4 @Required 注解有什么作用【重要】
      • 4.5 @Autowired 注解有什么作用【重要】
      • 4.6 @Autowired和@Resource之间的区别【重要】
      • 4.7 @RequestMapping 注解有什么用?【重要】
    • 五、Spring数据访问
      • 5.1 解释对象/关系映射集成模块
      • 5.2 在Spring框架中如何更有效地使用JDBC?
      • 5.3 解释JDBC抽象和DAO模块
      • 5.4 spring DAO 有什么用?
      • 5.5 spring JDBC API 中存在哪些类?
      • 5.6 JdbcTemplate是什么
      • 5.7 使用Spring通过什么方式访问Hibernate?
      • 5.8 如何通过HibernateDaoSupport将Spring和Hibernate结合起来?
      • 5.9 Spring支持的事务管理类型? spring 事务实现方式有哪些?【重要】
        • 5.9.1 声明式事务
      • 5.10 Spring事务的实现方式和实现原理
      • 5.11 说一下Spring的事务传播行为【重要】
        • 5.11.1 事务传播行为分类
        • 5.11.2 事务的配置
      • 5.12 说一下 spring 的事务隔离?【重要】
        • 5.12.1 事务隔离级别
        • 5.12.2 Spring隔离级别
      • 5.13 Spring框架的事务管理有哪些优点?
      • 5.14 你更倾向用那种事务管理类型?
      • 5.15 Spring 是如何管理事务的?
    • 六、Spring面向切面编程(AOP)
      • 6.1 什么是AOP
      • 6.2 JDK动态代理和CGLIB动态代理的区别【重要】
        • 6.2.1 JDK动态代理和CGLIB动态代理的使用选择
        • 6.2.2 JDK动态代理和CGLIB动态代理的区别
      • 6.3 解释一下Spring AOP里面的几个名词【重要】
      • 6.4 如何理解 Spring 中的代理?
      • 6.5 Spring在运行时通知对象
      • 6.6 Spring只支持方法级别的连接点
      • 6.7 在Spring AOP 中,关注点和横切关注点的区别是什么?
      • 6.8 Spring通知有哪些类型?【重要】
      • 6.9 当应用程序中包含多个切面类的时候,具体的执行顺序是什么样?
      • 6.10 Spring AOP的实现原理?
      • 6.11 AOP注解使用的小demo

  本系列文章:
    后端开发知识点汇总(一)基础篇
    后端开发知识点汇总(二)集合篇
    后端开发知识点汇总(三)多线程篇
    后端开发知识点汇总(四)JVM篇
    后端开发知识点汇总(五)设计模式篇
    后端开发知识点汇总(六)Linux篇
    后端开发知识点汇总(七)Tomcat篇
    后端开发知识点汇总(八)Hibernate篇
    后端开发知识点汇总(九)Mybatis篇
    后端开发知识点汇总(十)Spring篇
    后端开发知识点汇总(十一)Spring MVC篇
    后端开发知识点汇总(十二)Mysql篇
    后端开发知识点汇总(十三)Spring Boot篇

一、Spring概述

1.1 什么是spring?

  Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。
  Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
  Spring可以做很多事情,它为企业级开发提供给了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)。
  为了降低Java开发的复杂性,Spring采取了以下4种关键策略:
   基于POJO的轻量级和最小侵入性编程
   通过依赖注入和面向接口实现松耦合
   基于切面和惯例进行声明式编程
   通过切面和模板减少样板式代码
  Spring的核心配置文件是applicationContext.xml
  Spring的应用场景:JavaEE企业应用开发,包括SSH、SSM等。

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、方便解耦,简化开发(IOC)
      Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。
  • 2、AOP编程的支持
      Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
  • 3、声明式事务的支持
      只需要通过配置就可以完成对事务的管理,而无需手动编程。
  • 4、方便程序的测试
      Spring对Junit4支持,可以通过注解方便的测试Spring程序。
  • 5、方便集成各种优秀框架
      Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
  • 6、降低JavaEE API的使用难度
      Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

缺点

  • 1、Spring依赖反射,反射影响性能
  • 2、使用门槛升高,入门Spring需要较长时间

1.4 Spring由哪些模块组成?【基础】

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

  • 1、Spring core(核心容器)
      核心容器提供spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。BeanFactory使用依赖注入的方式提供给组件依赖。主要实现控制反转IoC和依赖注入DI、Bean配置以及加载。
  • 2、Spring AOP(Spring面向切面编程)
      通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。AOP把一个业务流程分成几部分,例如权限检查、业务处理、日志记录,每个部分单独处理,然后把它们组装成完整的业务流程。每个部分被称为切面关注点
  • 3、Spring context(Spring上下文)
      Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。提供框架式Bean访问方式,其他程序可以通过Context访问Spring的Bean资源。
  • 4、Spring ORM(对象关系映射模块)
      Spring 与所有的主要的ORM框架都集成的很好,包括hibernate、JDO实现、TopLink和IBatis SQL Map等。Spring为所有的这些框架提供了模板之类的辅助类,达成了一致的编程风格。
      Spring的ORM模块对ORM框架如Hibernate等进行了封装,Spring能够管理、维护Hibernate,使用时可直接继承HibernateDaoSupport类,该类内置一个HibernateTemplate。Hibernate的配置也转移到Spring配置文件中。
  • 5、Spring JDBC
      提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。主要是提供 JDBC 模板方式、 关系数据库对象化方式、 SimpleJdbc 方式、 事务管理来简化 JDBC 编程, 主要实现类是 JdbcTemplate、 SimpleJdbcTemplate 以及 NamedParameterJdbcTemplate。
  • 6、Spring Web
      Web模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。Web层使用Web层框架,可选的,可以是Spring自己的MVC框架,或者提供的Web框架,如Struts、Webwork、tapestry和jsf。
      Web模块用于整合Web框架,将Web框架也纳入Spring的管理之中。如Spring提供继承方式与代理方式整合Struts,继承方式不需要更改任何配置文件,只把Action继承自ActionSupport即可,但会对Spring产生依赖。代理方式需要在struts-config.xml中配置< controller>,由Spring全盘代理,因此可以使用Spring的各种资源、拦截器等。
  • 7、Spring Beans
      提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean
  • 8、Spring Aspects
      提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的AOP框架。
  • 9、Spring MVC
      MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。Spring的MVC框架提供清晰的角色划分:控制器、验证器、命令对象、表单对象和模型对象、分发器、处理器映射和视图解析器。Spring支持多种视图技术。

  Spring各模块之间的依赖关系:
后端开发知识点汇总(十)Spring篇_第2张图片

1.5 Spring 框架中都用到了哪些设计模式?【重要】

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

1.6 Spring框架中有哪些不同类型的事件【重要】

  Spring的事件通知机制是一项很有用的功能,使用事件机制可以将相互耦合的代码解耦,从而方便功能的修改与添加。
  举个例子,假设有一个添加评论的方法,在评论添加成功之后需要进行修改redis缓存、给用户添加积分等等操作。在以前的代码中,我使用观察者模式来解决这个问题。不过Spring中已经存在了一个升级版观察者模式的机制,这就是监听者模式。通过该机制我们就可以发送接收任意的事件并处理。

监听者模式包含了一个监听者Listener与之对应的事件Event,还有一个事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后执行事件相应的方法。

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

  • 1、上下文更新事件
     在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
  • 2、上下文开始事件
     当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
  • 3、上下文停止事件
     当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
  • 4、上下文关闭事件
     当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
  • 5、请求处理事件
     在Web应用中,当一个http请求(request)结束触发该事件。

  如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知,其实就是接下来要说的自定义事件。

  除了上面介绍的事件以外,还可以通过扩展ApplicationEvent 类来开发自定义的事件。示例:

public class CustomApplicationEvent extends ApplicationEvent{
     
    public CustomApplicationEvent ( Object source, final String msg ){
     
        super(source);
        System.out.println("Created a Custom event");
    }
}

  为了监听这个事件,还需要创建一个监听器:

public class CustomEventListener implements ApplicationListener < CustomApplicationEvent >{
     
    @Override
    public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
     
        //handle event
    }
}

  之后通过applicationContext接口的publishEvent()方法来发布自定义事件:

CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
applicationContext.publishEvent(customEvent);

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

  Spring 应用一般有以下组件:

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

二、控制反转(IOC)

2.1 什么是Spring IOC 容器?【重要】

  控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器
后端开发知识点汇总(十)Spring篇_第3张图片
  Spring IOC 负责创建对象,管理对象,装配对象,配置对象,并且管理这些对象的整个生命周期
  再稍微扩展开来介绍的话,即:IoC容器就是具有依赖注入功能的容器,IoC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中创建相关的对象,应用程序由IoC容器进行组装。
  Spring IoC容器如何知道哪些是它管理的对象呢?这就需要配置文件,Spring IoC容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。由IoC容器管理的那些组成应用程序的对象我们就叫它Bean, Bean就是由Spring容器初始化、装配及管理的对象。
  IOC容器的职责是:

  • 实例化bean
  • 把bean关联在一起
  • 配置bean
  • 管理bean的整个生命周期

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

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

2.3 IOC的优点是什么?

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

  同时提一下IOC的缺点:对象生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。

2.4 Spring IoC 的实现机制【基础】

  Spring 中的 IoC 的实现原理就是工厂模式加反射机制。图示:
后端开发知识点汇总(十)Spring篇_第4张图片

2.5 Spring 的 IoC支持哪些功能

  • 1、依赖注入
  • 2、依赖检查
  • 3、自动装配
  • 4、支持集合
  • 5、指定初始化方法和销毁方法
  • 6、支持回调某些方法(但是需要实现 Spring 接口,略有侵入)

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

2.6 BeanFactory 和 ApplicationContext有什么区别?【重要】

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

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

  BeanFactory和ApplicationContext 接口及其子类图:
后端开发知识点汇总(十)Spring篇_第5张图片

  • 1、依赖关系
      BeanFactory:是Spring里面最顶级的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
      ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能
  1. 继承MessageSource,因此支持国际化
  2. 统一的资源文件访问方式
  3. 提供在监听器中注册bean的事件
  4. 同时加载多个配置文件。
  5. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
  • 2、加载方式
      BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
      ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
      相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
  • 3、注册方式
      BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
  • 4、底层资源的访问
      ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource,而BeanFactory是没有扩展ResourceLoader

绝大多数情况下建议使用ApplicationContext,因为ApplicationContext包含BeanFactory的所有功能,因此通常建议优先于BeanFactory使用它。

2.7 ApplicationContext通常的实现是什么?【重要】

ApplicationContext常用实现类 作用
AnnotationConfigApplicationContext(常用) 从一个或多个基于Java的配置类中加载上下文定义,适用于Java注解的方式
ClassPathXmlApplicationContext(常用) 从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式 ,需要正确设置classpath因为这个容器将在classpath里找bean配置
FileSystemXmlApplicationContext (常用) 从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件,XML Bean 配置文件的全路径名必须提供给它的构造函数
AnnotationConfigWebApplicationContext 专门为web应用准备的,适用于注解方式
XmlWebApplicationContext 从Web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式

 ClassPathXmlApplicationContext和FileSystemXmlApplicationContext 的路径加载区别:

  1. ClassPathXmlApplicationContext默认文件路径是src下那一级;
  2. FileSystemXmlApplicationContext 默认获取的是项目路径,默认文件路径是项目名下一级,与src同级。

2.8 什么是Spring的依赖注入,几种形式?【重要】

  依赖注入(DI)和控制反转(IOC)是同一个概念。所谓依赖注入, 是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是 依赖于外部的 注入
  依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入,Setter方法注入和构造器注入三种方式。其中接口注入由于在灵活性和易用性比较差,从Spring4开始已被废弃。

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

bean下面的子标签是constructor-arg开头

    <bean id="userId1" class="com.cc.study.di.User" lazy-init="true">
        <constructor-arg  type="java.lang.Integer" value="1"></constructor-arg>
        <constructor-arg type="java.lang.String" value="bbb"></constructor-arg>
    </bean>
  • 2、Setter方法注入
    Setter方法注入是方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。

bean下面的子标签是property开头

<!-- Spring IOC注入方式: Setter方法注入 -->
    <bean id="injectionService" class="com.service.InjectionServiceImpl">
    	<property name="injectionDAO" ref="injectionDAO"></property>
    </bean>
    
    <bean id="injectionDAO" class="com.dao.InjectionDAOImpl"></bean>
  • 3、自动装配
     即用@Autowired来自动装配对象。Java代码示例:
@Component  
public class Programmer {
       
      
    @Autowired  
    Computer computer;  
}

@Component  
public class Computer {
      
}

 配置文件示例:

	<!--表示扫描整个com.spring.demo04包 -->
    <context:component-scan base-pakage="com.spring.demo04"> 

2.9 依赖注入(DI)有什么优势

  1. 依赖注入减少了一个类和其他类的耦合并且减少了依赖;
  2. 通过让构造一些假的依赖项, 可以方便的对Client进行独立的测试;
  3. 可以方便的分离出配置文件
  4. 减少应用程序中的构造代码
  5. Client对依赖项的变化不敏感。

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

  • 1、Setter方法注入
     1、设值注入需要该Bean包含这些属性的setter方法
     2、对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化器依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
     3、尤其是在某些属性可选的情况下,多参数的构造器显得更加笨重。

  • 2、构造注入
     1、构造注入需要该Bean包含带有这些属性的构造器
     2、构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。例如,组件中其他依赖关系的注入,常常要依赖于DataSrouce的注入。采用构造注入,可以在代码中清晰的决定注入顺序。
     3、对于依赖关系无需变化的Bean,构造注入更有用处。因为没有Setter方法,所有的依赖关系全部在构造器内设定。因此,无需担心后续的代码对依赖关系产生破坏。
     4、依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。

  在两种方式的选择上:最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖

2.11 实例化bean的三种方式

  • 1、构造器实例化bean
     假设有一个Person类,有String name,Integer age两个属性,通过构造器的方法的配置方式为:
<bean id="person" class="com.spring.demo.Person">
        <constructor-arg name="name" value="等风的草"/>
        <constructor-arg name="age" value="21"/>
bean>
  • 2、静态工厂实例化bean
     当采用静态工厂方法创建bean时,除了需要指定class属性外,还需要通过factory-method属性来指定创建bean实例的工厂方法。
     假设有这样的静态工厂PersonStaticFactory类:
public static Person createInstance() {
     
    return new Person();
}

 配置文件(class:指定静态工厂类;factory-method:指定哪个方法是工厂方法):

<bean id="personStaticFactoryParams" class="com.spring.demo.PersonStaticFactory" factory-method="createInstance">
    <constructor-arg name="name" value="等风的草"/>
    <constructor-arg name="age" value="21"/>
bean>
  • 3、实例工厂实例化bean
     示例代码:
public class InstanceFactory {
     

    public Person createInstance() {
     
        return new Person();
    }
}

 配置文件示例( factory-bean:指定使用哪个工厂实例;factory-method:指定使用哪个工厂实例的方法):


<bean id="instancefactory" class="com.spring.demo.InstanceFactory"/>

<bean id="personInstance" factory-bean="instancefactory" factory-method="createInstance"/>

<bean id="personInstanceWithParams" factory-bean="instancefactory" factory-method="createInstance">
    <constructor-arg name="name" value="等风的草"/>
    <constructor-arg name="age" value="21"/>
bean>

三、Spring Beans

3.1 什么是Spring beans

  Spring beans 是那些形成Spring应用的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。

3.2 Spring有几种配置方式

  有三种:基于xml的配置、基于注解的配置、基于Java的配置。

  • 1、XML配置文件
      示例代码:
    <bean id="jackma" class="com.test.User">
        <property name="name" value="jackma" />
        <property name="age" value="55" />
        <property name="dog" ref="jm" />
     </bean>
  • 2、基于注解的配置
//控制层
@Controller
public class UserController {
     
}

//服务层
@Service
public interface UserService {
     
}
  • 3、基于java的配置
      Spring3.0以后,提供了Java配置的能力,Spring4.x和SpringBoot都推荐使用Java配置。
@Configuration
public class DemoConfig {
     
    @Bean
    public User jackma(){
     
        return new User();
    }
    @Bean
    public Dog dog(){
     
        return  new Dog();
    }
}

@Component("jackma")
public class User {
     
    private String name;
    private int age;
    private Dog dog;

  //get,set方法略
}

3.3 解释Spring支持的几种bean的作用域【重要】

  当定义一个Bean在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。

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

类别 说明 注解
singleton 默认值 , 容器初始时创建 bean 实例, 在整个容器的生命周期内只创建这一个bean @Scope(“singleton”)
prototype 容器初始化时不创建bean的实例,而在每次请求时都创建一个新的Bean的实例 @Scope(“prototype”)
request 在每一次http请求时会创建一个实例,该实例仅在当前http request有效 @Scope(“request”)
session 在每一次http请求时会创建一个实例,该实例仅在当前http session有效 @Scope(“session”)
globalSession 全局Session,供不同的portlet共享,portlet好像是类似于servlet的Web组件 @SessionScope

   spring4.x的版本中包含两种作用域:request:每次发送请求都会有一个新的对象;session:每一次会话都会有一个新的对象。这两种几乎不用,因此在5版本的时候被淘汰了。

  • 1、Singleton
      当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
      Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,每次获取到的对象都是同一个对象。要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
  • 2、Prototype
      当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。
      Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
  • 3、Request
      当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
  • 4、Session
      当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
  • 5、Global Session
      当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。

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

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

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

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

详述
  如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
  对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。

有状态对象(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。
无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。

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

  在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题
  ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
  ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

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

后端开发知识点汇总(十)Spring篇_第6张图片
  1、Spring对bean进行实例化
  2、Spring将值和bean的引用注入到bean对应的属性中
  3、如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
  4、如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
  5、如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
  6、如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
  7、如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;
  8、如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
  9、此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁
  10、如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

总结二:

  1. 实例化Bean:
     对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
  2. 设置对象属性(依赖注入):
     实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
  3. 处理Aware接口:
     接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
      ①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
      ②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
      ③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
  4. BeanPostProcessor:
     如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
  5. InitializingBean 与 init-method:
     如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
  6. 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
    以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
  7. DisposableBean
    当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
  8. destroy-method
    最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

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

  bean生命周期表示bean的创建到销毁。

  1. 如果bean是单例,容器在启动的时候会创建好,关闭的时候会销毁创建的bean;
  2. 如果bean是多例,获取的时候创建对象,销毁的时候不会有任何的调用。

  在创建对象的时候,我们可以根据需要调用初始化和销毁的方法:

    <bean id="address" class="com.mashibing.bean.Address" 
    	init-method="init" destroy-method="destory">bean>

  在具体的实体类里即可以实现上面的两个方法,示例:

    public void init(){
     
        System.out.println("对象被初始化");
    }
    
    public void destory(){
     
        System.out.println("对象被销毁");
    }

3.8 什么是Spring的内部bean?

  在Spring框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现,内部bean通常是匿名的,它们的Scope一般是prototype。
  比如,在我们的应用程序中,一个Customer类引用了一个Person类,我们的要做的是创建一个Person的实例,然后在Customer内部使用。 示例:

public class Customer{
     
    private Person person;
    //Setters and Getters
}
public class Person{
     
    private String name;
    private String address;
    private int age;

    //Setters and Getters
}

  内部bean的声明方式如下:

<bean id="CustomerBean" class="com.howtodoinjava.common.Customer">
    <property name="person">
        <!-- This is inner bean -->
        <bean class="com.howtodoinjava.common.Person">
            <property name="name" value="lokesh" />
            <property name="address" value="India" />
            <property name="age" value="34" />
        </bean>
    </property>
</bean>

3.9 在 Spring中如何注入一个java集合?【重要】

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

  1. ,用于注入一列值,允许有相同的值。示例:
        <!-- 给list注入值 可以有相同的多个对象  -->
        <property name="empList">
            <list>
                <ref bean="emp1" />
                <ref bean="emp2"/>
            </list>
        </property>
  1. ,用于注入一组值,不允许有相同的值。示例:
        <!-- 给set注入值 不能有相同的对象 -->
        <property name="empSets">
            <set>
                <ref bean="emp1" />
                <ref bean="emp2"/>
            </set>
        </property>
  1. ,用于注入一组键值对,键和值都可以为任意类型。示例:
        <!-- 给map注入值 只要map中的key值不一样就可以装配value -->
        <property name="empMap">
            <map>
                <entry key="1" value-ref="emp1" />
                <entry key="2" value-ref="emp2" />
            </map>
        </property>
  1. ,用于注入一组键值对,键和值都只能为String类型。 示例:
        <!-- 给属性集合配置 -->
        <property name="pp">
            <props>
                <prop key="pp1">hello</prop>
                <prop key="pp2">world</prop>
            </props>

3.10 什么是bean装配?

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

3.11 什么是bean的自动装配?【基础】

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

3.12 spring 自动装配 bean 有哪些方式?【基础】

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

  1. no
     默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。示例:
<bean id="cat_c" class="com.spring.auto.autowire.Cat">bean>
    <bean id="dog_d" class="com.spring.auto.autowire.Dog">bean>
    <bean id="test" class="com.spring.auto.autowire.Person">
        <property name="cat" ref="cat_c"/>
        <property name="dog" ref="dog_d"/>
        <property name="say" value="测试"/>
    bean>
  1. byName
     通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。示例:
<bean id="cat" class="com.spring.auto.autowire.Cat">bean>
    <bean id="dog" class="com.spring.auto.autowire.Dog">bean>
    <bean id="test" class="com.spring.auto.autowire.Person" autowire="byName">
        <property name="say" value="测试"/>
    bean>

该模式表示根据Property的Name自动装配,如果一个bean的name,和另一个bean中的Property的name相同,则自动装配这个bean到Property中。当一个bean节点带有 autowire byName的属性时,将查找其类中所有的set方法名,获得将set去掉并且首字母小写的字符串,然后去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。

  1. byType
     通过参数的数据类型进行自动装配。示例:
<bean id="cat" class="com.spring.auto.autowire.Cat">bean>
    <bean id="dog" class="com.spring.auto.autowire.Dog">bean>
    <bean id="test" class="com.spring.auto.autowire.Person" autowire="byType">
        <property name="say" value="测试"/>
    bean>
  1. constructor
     利用构造函数进行装配,并且构造函数的参数通过byType进行装配。示例:
<bean id="cat" class="com.spring.auto.autowire.Cat">bean>
    <bean id="dog" class="com.spring.auto.autowire.Dog">bean>
    <bean id="test" class="com.spring.auto.autowire.Person" autowire="constructor">
        <constructor-arg index="2" value="55">constructor-arg>
    bean>
  1. autodetect
     自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

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

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

< context:annotation-config />

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

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

  @Autowired可用于:构造函数、成员变量、Setter方法。最常见的使用示例:

@Service
public class UserService {
     

    @Autowired
    private UserRepository userRepository;

    public void save(){
     
        userRepository.save();
    }
}

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

  自动装配的局限性是:
   重写:你仍需用 配置来定义依赖,意味着总要重写自动装配。
   基本数据类型:不能自动装配简单的属性,如基本数据类型,String字符串,和类。
   模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

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

  可以。

四、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 有何区别?【重要】

  在Spring2.5版本中,引入了更多的Spring类注解:

@Component、@Service、@Controller。

  @Component是一个通用的Spring容器管理的单例bean组件注解。而@Repository, @Service, @Controller就是针对不同的使用场景所采取的特定功能化的注解组件。

总结:

  1. @Component是通用注解,理论上可以在任意的类上进行添加,在扫描的时候都会完成bean的注册其他三个注解是这个注解的拓展,并且具有了特定的功能 。
  2. @Repository注解在持久层中,对数据库进行操作
  3. @Controller是控制层的注解,具有将请求进行转发,重定向的功能,包括调用Service层的方法
  4. @Service是业务逻辑层注解,这个注解只是标注该类处于业务逻辑层

  用这些注解对应用进行分层之后,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了项目的维护和开发。

4.4 @Required 注解有什么作用【重要】

  @Required 注解主要用在 setter 方法上,它表示该 setter 方法的属性必须要在配置时注入值。否则就会报 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的作用是自动注入Bean。示例,在下面的代码中就相当于直接给userDao变量实例化了,可以直接用:

public class UserService {
     
    @Autowired
    private UserDao userDao; 
}

  如上代码所示,spring容器会找到类型为UserDao的类,然后将其注入进来。这样会产生一个问题,当一个类型有多个bean值的时候,会造成无法选择具体注入哪一个的情况,spring容器在启动时也会抛出BeanCreationException。这个时候我们需要配合着@Qualifier使用。@Qualifier告诉spring具体去装配哪个对象。
  当使用AutoWired注解的时候,自动装配的时候是根据类型(在上面的例子中,即类型为UserDao的类)来装配的:

  1. 如果只找到一个UserDao类型的类,则直接进行赋值;
  2. 如果没有找到UserDao类型的类,则直接抛出异常;
  3. 如果找到多个UserDao类型的类,那么会按照变量名(此处的变量名为类名首字母小写)作为id继续匹配:
     1)匹配上直接进行装配
     2)如果匹配不上则直接报异常
  4. 还可以不使用变量名,使用@Qualifier注解来指定id的名称,当使用@Qualifier注解的时候也会有两种情况:
     1)找到,则直接装配
     2)找不到,就会报错

  @Qualifier和@Autowired结合使用的示例:

public class UserService {
     
    @Autowired
    @Qualifier(name="userDao1")    
    private UserDao userDao; 
}

  在上面的介绍中,找不到对应的类,都会报错,如果想不报错,可以使用@Autowired(required=false)。该注解表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。

  @AutoWired添加到方法上的时候,此方法在创建对象的时候会默认调用,同时方法中的参数会进行自动装配。@Qualifier注解也可以定义在方法的参数列表中,可以指定当前属性的id名称,示例:

    @Autowired
    public void test(@Qualifier("personDao") PersonDao personDao123) {
     
        System.out.println("test");
        personDao123.update();
    }

4.6 @Autowired和@Resource之间的区别【重要】

  • 1、@Autowired
      @Autowired为Spring提供的注解
      @Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,示例:
public class UserService {
     
    @Autowired
    private UserDao userDao; 
}
  • 2、@Resource
      @Resourc是jdk提供的功能,需要导入包javax.annotation.Resource。
      @Resource默认按照ByName自动注入。示例:
public class UserService {
     
    @Resource  
    private UserDao userDao; 
    @Resource(name="studentDao")  
    private StudentDao studentDao; 
    @Resource(type="TeacherDao")  
    private TeacherDao teacherDao; 
    @Resource(name="manDao",type="ManDao")  
    private ManDao manDao; 
}  

  ①如果同时指定了name(变量名)和type(类型),则从Spring上下文中按照先name再type,找唯一匹配的bean进行装配,找不到则抛出异常。
  ②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
  ③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
  ④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,那就按类型就行匹配。

总结:

  1. @AutoWired是spring中提供的注解@Resource是jdk中定义的注解,依靠的是java的标准;
  2. @AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在@Resource默认是按照名字进行匹配的,同时可以指定name属性;
  3. @AutoWired只适合spring框架,而@Resource扩展性更好

4.7 @RequestMapping 注解有什么用?【重要】

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

  1. 类级别:映射请求的 URL
      示例:
@Controller("accountController")
@RequestMapping("/account")
public class AccountController {
     

}
  1. 方法级别:映射 URL 以及 HTTP 请求方法
      示例:
@RequestMapping(value="/removeAccount",params= {
     "accountName","money>100"})
public String removeAccount() {
     
	System.out.println("删除了账户");
	return "success";
}

五、Spring数据访问

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

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

  对象关系映射,简单讲就是Java对象 Object 和关系型数据库 Relationship 之间的映射Mapping, 即ORM。
  为什么要使用ORM? 因为面向对象的概念,使得操作关系型数据库也可以用操作对象那样处理。操作关系型数据库,就像操作Java对象一样,更容易以面向对象的方式理解。
  常见的ORM框架有:Hibernate、TopLink、Castor JDO、Apache OJB等。
  ORM的实现原理:要实现JavaBean的属性到数据库表的字段的映射,任何ORM框架不外乎是读某个配置文件把JavaBean的属 性和数据库表的字段自动关联起来,当从数据库Query时,自动把字段的值塞进JavaBean的对应属性里,当做INSERT或UPDATE时,自动把 JavaBean的属性值绑定到SQL语句中。

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

  使用Spring JDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate 。JdbcTemplate类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。

5.3 解释JDBC抽象和DAO模块

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

5.4 spring DAO 有什么用?

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

5.5 spring JDBC API 中存在哪些类?

  1. JdbcTemplate
  2. SimpleJdbcTemplate
  3. NamedParameterJdbcTemplate
  4. SimpleJdbcInsert
  5. SimpleJdbcCall

5.6 JdbcTemplate是什么

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

  JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。而多的这个template,就是模板,是Spring框架为我们提供的,所以JDBCTemplate就是Spring对JDBC的封装,通俗点说就是Spring对jdbc的封装的模板。
  JdbcTemplate对JDBC的差别在哪? jdbc需要每次进行数据库连接, 然后处理SQL语句,传值,关闭数据库。甚至有时还可能会出现数据库忘记关闭导致连接被占用,在以后的工作中,客户的需求肯定不是一成不变的,这就导致经常会改动数据库内容。通过JDBCtemplate我们只需更改需要更改的那一部分内容就可以了,不需要进行全局修改,Spring将替我们完成所有的JDBC底层细节处理工作。
  jdbcTemplate有什么缺点? 必须于Spring框架结合在一起使用、不支持数据库跨平台、默认没有缓存。

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

  • 1、注入 sessionFactory
      在 spring 配置文件中,对 Dao 注入 sessionFactory ,即:
<bean id="classDao" class="cn.jmu.data.dao.impl.ClassImpl">
    <property name="sessionFactory">
        <ref local="sessionFactory" />
    </property>
</bean>

  sessionFactory 依赖注入的不是给 Dao 层中的类,而是给 HibernateDaoSupport,所以在 Dao 层中类继承 HibernateDaoSupport ,即可通过 this.getHibernateTemplate() 来对数据库进行操作:

更新数据: this.getHibernateTemplate().update(bo);
查询数据: this.getHibernateTemplate().find(bo);
添加数据: this.getHibernateTemplate().save(bo) ;
删除数据: this.getHibernateTemplate().delete(bo);

  • 2、注入 hibernateTemplate
      这种方法本质跟上面注入 sessionFactory 一样,只不过再进行一层包装,这样最大的好处就是 Dao中的类就不用再继承 HibernateDaoSupport ,不过在这之前要先要配置好 hibernateTemplate ,即:
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
    <property name="sessionFactory">
        <ref bean="sessionFactory"/>
    </property>
</bean>

  再对要用到 hibernateTemplate 的 Dao 进行注入依赖,即:

<bean id="ClassDao" class="cn.jmu.data.dao.impl.ClassImpl">
    <property name="hibernateTemplate">
        <ref bean="hibernateTemplate"/>
    </property>
</bean>

  在 Dao 层的类就要添加 hibernateTemplate 对象,来对应配置文件中所注入的依赖:

private HibernateTemplate hibernateTemplate;
public HibernateTemplate getHibernateTemplate() {
     
    return hibernateTemplate;
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
     
    this.hibernateTemplate = hibernateTemplate;
}

  hibernateTemplate 对数据的增删查给就跟上面的一样,即:

更新数据: hibernateTemplate().update(bo);
查询数据: hibernateTemplate().find(bo);
添加数据: hibernateTemplate().save(bo) ;
删除数据: hibernateTemplate().delete(bo);

  • 3、注入 jdbcTemplate
      先配置好 jdbcTemplate :
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource">
        <ref bean="dataSource" />
    </property>
</bean> 

  再对要用到 jdbcTemplate 的Dao 进行注入依赖,即:

<bean id="classDao" class="cn.jmu.data.dao.impl.ClassImpl">
    <property name="jdbctemplate">
        <ref bean="jdbcTemplate" />
    </property>
</bean>

  在 Dao 层的类就要添加 jdbctemplate 对象,来对应配置文件中所注入的依赖:

protected JdbcTemplate jdbctemplate;
public JdbcTemplate getJdbctemplate() {
     
    return jdbctemplate;
}
public void setJdbctemplate(JdbcTemplate jdbctemplate) {
     
    this.jdbctemplate = jdbctemplate;
}

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

  该问题的答案就是上个问题的前两种方式。
  用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步:

  1. 配置the Hibernate SessionFactory
  2. 继承HibernateDaoSupport实现一个DAO
  3. 在AOP支持的事务中装配

5.9 Spring支持的事务管理类型? spring 事务实现方式有哪些?【重要】

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

  1. 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
  2. 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

  Spring 事务实现方式:
   1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
   2)基于 TransactionProxyFactoryBean的声明式事务管理
   3)基于 @Transactional 的声明式事务管理
   4)基于Aspectj AOP配置事务

5.9.1 声明式事务

  声明式事务:在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。

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

  Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
  对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:

1.获取连接 Connection con = DriverManager.getConnection()
2.开启事务con.setAutoCommit(true/false);
3.执行CRUD
4.提交事务/回滚事务 con.commit() / con.rollback();
5.关闭连接 conn.close();

  使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。那么Spring是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢

  1. 配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
  2. spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
  3. 真正的数据库层的事务提交和回滚是通过bin log或者redo log实现的。

5.11 说一下Spring的事务传播行为【重要】

5.11.1 事务传播行为分类

  spring事务的传播行为说的是,当多个事务同时存在(当一个事务方法被另一个事务方法调用)的时候,Spring如何处理这些事务的行为。 事务的传播行为,默认值为 Propagation.REQUIRED。其七个分类意义如下:

事务传播行为类型 说明
REQUIRED (重要) 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
REQUIRES_NEW(重要) 当前的方法必须启动新事务,并且在它自己的事务内运行,如果有事务正在运行,应该将它挂起。
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED 当前方法不应该运行在事务中,如果当前存在事务,就把当前事务挂起。
NEVER 当前方法不应该运行在事务中,如果当前存在事务,则抛出异常。
MANDATORY 当前的方法必须运行在事务内部,如果当前没有事务,就抛出异常。
NESTED(重要) 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新事物,并在它自己的事务内运行

  使用示例:

@Transactional(propagation = Propagation.REQUIRED)

  展开来说:

  • 1、REQUIRED
      假如当前正要运行的事务不在另外一个事务里,那么就起一个新的事务 比方说,ServiceB.methodB的事务级别定义PROPAGATION_REQUIRED, 那么因为执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务。这时调用ServiceB.methodB,ServiceB.methodB看到自己已经执行在ServiceA.methodA的事务内部。就不再起新的事务。
      而假如ServiceA.methodA执行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的不论什么地方出现异常。事务都会被回滚。即使ServiceB.methodB的事务已经被提交,可是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚。
  • 2、REQUIRES_NEW
      假如ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW。那么当运行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起。ServiceB.methodB会起一个新的事务。等待ServiceB.methodB的事务完毕以后,ServiceA.methodA才继续运行。
      他与PROPAGATION_REQUIRED 的事务差别在于事务的回滚程度了。由于ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。假设ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚。ServiceB.methodB是不会回滚的。假设ServiceB.methodB失败回滚,假设他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
  • 3、SUPPORTS
      假设当前在事务中,就以事务的形式执行。假设当前不在一个事务中,那么就以非事务的形式执行。
  • 4、MANDATORY
      必须在一个事务中执行。也就是说,他仅仅能被一个父事务调用。否则,他就要抛出异常。
  • 5、NOT_SUPPORTED
      当前不支持事务。比方ServiceA.methodA的事务级别是PROPAGATION_REQUIRED 。而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,那么当执行到ServiceB.methodB时。ServiceA.methodA的事务挂起。而他以非事务的状态执行完,再继续ServiceA.methodA的事务。
  • 6、NEVER
      不能在事务中执行。
      如果ServiceA.methodA的事务级别是PROPAGATION_REQUIRED。 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了。
  • 7、NESTED
      如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
      PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint.嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
      由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back。

5.11.2 事务的配置

  配置方式有两种:配置文件的方式和注解的方式。

  • 1、配置文件的方式
      示例:
<tx:advice id="txAdvice" transaction-manager="txManager"> 
      <tx:attributes>  
      <!--设置所有匹配的方法,然后设置传播级别和事务隔离-->
           <tx:method name="save*" propagation="REQUIRED" /> 
           <tx:method name="add*" propagation="REQUIRED" /> 
           <tx:method name="create*" propagation="REQUIRED" /> 
           <tx:method name="insert*" propagation="REQUIRED" /> 
           <tx:method name="update*" propagation="REQUIRED" /> 
           <tx:method name="merge*" propagation="REQUIRED" /> 
           <tx:method name="del*" propagation="REQUIRED" /> 
           <tx:method name="remove*" propagation="REQUIRED" /> 
           <tx:method name="put*" propagation="REQUIRED" /> 
           <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> 
           <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> 
          <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> 
          <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> 
          <tx:method name="*" propagation="SUPPORTS" read-only="true" /> 
     </tx:attributes> 
</tx:advice> 
  • 2、注解的方式
<!--开启注解的方式--> 
<tx:annotation-driven transaction-manager="transactioManager" />

  @Transactional(propagation=Propagation.REQUIRED)
   如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)。
  @Transactional(propagation=Propagation.NOT_SUPPORTED)
   容器不为这个方法开启事务。
  @Transactional(propagation=Propagation.REQUIRES_NEW)
   不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务。
  @Transactional(propagation=Propagation.MANDATORY)
   必须在一个已有的事务中执行,否则抛出异常。  @Transactional(propagation=Propagation.NEVER)
   必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)。
  @Transactional(propagation=Propagation.SUPPORTS)
   如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务。

5.12 说一下 spring 的事务隔离?【重要】

  什么是事务:事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。

5.12.1 事务隔离级别

名称 结果 脏读 不可重复读 幻读
Read UnCommitted(读未提交) 什么都不解决
Read Committed(读提交) 解决了脏读的问题
Repeatable Read(重复读) (mysql的默认级别)解决了不可重复读 )
Serializable(序列化) 解决所有问题
  • READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。
  • READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。
  • REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
  • SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。

5.12.2 Spring隔离级别

    int ISOLATION_DEFAULT = -1;   默认采用数据库的隔离级
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;   //0000 0001 -> 1 
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;       //0000 0010-> 2
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;     //0000 0100-> 4
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;          // 0000 1000-> 8

  Mysql 默认:可重复读
  Oracle 默认:读已提交

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

  在以往的JDBCTemplate中事务提交成功,异常处理都是通过Try/Catch 来完成,而在Spring中。Spring容器集成了TransactionTemplate,封装了所有对事务处理的功能,包括异常时事务回滚,操作成功时数据提交等复杂业务功能。这都是由Spring容器来管理,大大减少了程序员的代码量,也对事务有了很好的管理控制。Hibernate中也有对事务的管理,hibernate中事务管理是通过SessionFactory创建和维护Session来完成。而Spring对 SessionFactory配置也进行了整合,不需要在通过hibernate.cfg.xml来对SessionaFactory进行设定。
  这样的话就可以很好的利用Sping对事务管理强大功能。避免了每次对数据操作都要现获得Session实例来启动事务/提交/回滚事务还有繁琐的Try /Catch操作。这些也就是Spring中的AOP(面向切面编程)机制很好的应用。一方面使开发业务逻辑更清晰、专业分工更加容易进行。另一方面就是应用Spirng AOP隔离降低了程序的耦合性使我们可以在不同的应用中将各个切面结合起来使用大大提高了代码重用度。

5.14 你更倾向用那种事务管理类型?

  spring两种事务:编程式事务和声明式事务。

  1. 声明式事务特点
     大多数Spring框架的用户选择声明式事务管理,因为声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

 声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

  1. 编程式事务
     编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

5.15 Spring 是如何管理事务的?

 Spring事务管理主要包括3个接口,Spring的事务主要是由它们(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三个共同完成的。

  1. PlatformTransactionManager:事务管理器–主要用于平台相关事务的管理
     主要有三个方法:
      commit 事务提交;
      rollback 事务回滚;
      getTransaction 获取事务状态。
  2. TransactionDefinition:事务定义信息–用来定义事务相关的属性,给事务管理器 PlatformTransactionManager使用,这个接口有下面四个主要方法:
      getIsolationLevel:获取隔离级别;
      getPropagationBehavior:获取传播行为;
      getTimeout:获取超时时间;
      isReadOnly:是否只读(保存、更新、删除时属性变为false–可读写,查询时为true–只读)
     事务管理器能够根据这个返回值进行优化,这些事务的配置信息,都可以通过配置文件进行配置。
  3. TransactionStatus:事务具体运行状态–事务管理过程中,每个时间点事务的状态信息。
     例如它的几个方法:
      hasSavepoint():返回这个事务内部是否包含一个保存点,
      isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚
      isNewTransaction():判断当前事务是否是一个新事务

 声明式事务的优缺点:

  1. 优点:不需要在业务逻辑代码中编写事务相关代码,只需要在配置文件配置或使用注解(@Transaction),这种方式没有侵入性。
  2. 缺点:声明式事务的最细粒度作用于方法上,如果像代码块也有事务需求,只能变通下,将代码块变为方法。

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

6.1 什么是AOP

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

6.2 JDK动态代理和CGLIB动态代理的区别【重要】

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

  java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

6.2.1 JDK动态代理和CGLIB动态代理的使用选择

  1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
  2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP;

如何强制使用CGLIB实现AOP?
1)添加CGLIB库
2)在spring配置文件中加入

  1. 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换。

6.2.2 JDK动态代理和CGLIB动态代理的区别

  1. JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  2. CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成final ;
  3. 两者的性能差别:在早期的时候,JDK动态代理性能弱于CGLIB动态代理,随着JDK的更新,两者性能以及差别不大。

6.3 解释一下Spring AOP里面的几个名词【重要】

  • 1、切面(Aspect)------>通知和切入点的结合
    通知和切入点共同定义了切面的全部内容

  • 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.4 如何理解 Spring 中的代理?

  将通知应用于目标对象后创建的对象称为代理对象。在客户端对象的情况下,目标对象和代理对象是相同的。
  Advice(通知) + Target Object (目标对象)= Proxy(代理对象)。

6.5 Spring在运行时通知对象

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

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

  因为Spring基于动态代理,所以Spring只支持方法连接点。

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

  关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。
  横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

6.8 Spring通知有哪些类型?【重要】

  在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。
  Spring切面可以应用5种类型的通知:

  • 1、前置通知(Before advice)
     在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
  • 2、返回后通知(After returning advice)
     在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 3、抛出异常后通知(After throwing advice)
     在方法抛出异常退出时执行的通知。
  • 4、后通知(After (finally) advice)
     当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
  • 5、环绕通知(Around Advice)
     包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

  五种通知的常见使用场景:

前置通知---->记录日志(方法将被调用)
环绕通知---->控制事务 权限控制
后置通知---->记录日志(方法已经成功调用)
异常通知---->异常处理 控制事务
最终通知---->记录日志(方法已经调用,但不一定成功)

  环绕通知的执行顺序是优于普通通知的
  同一个aspect,不同advice的执行顺序:

  1. 没有异常情况下的执行顺序:

around before advice
before advice
目标方法执行
around after advice
after advice
afterReturning

  如果出现异常的时候,在环绕通知中解决了,那么普通通知是接受不到的。如果想让普通通知接收到,需要在环绕通知中进行抛出 throw throwable。
2. 有异常情况下的执行顺序:

around before advice
before advice
目标方法执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生

6.9 当应用程序中包含多个切面类的时候,具体的执行顺序是什么样?

按照切面类的名称的首字母进行排序操作,按照字典序。比如有两个切面类:LogUtil、SecurityUtil,就是LogUtil先执行。
 如果需要人为地规定顺序,可以在切面类上添加@Order注解同时可以添加具体的值,值越小,越优先。示例:

@Aspect
@Component
@Order(100)
public class SecurityUtil {
     
}

@Aspect
@Component
@Order(200)
public class LogUtil {
     
}

 此时就是SecurityUtil切面先执行。

6.10 Spring AOP的实现原理?

 Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
 Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

6.11 AOP注解使用的小demo

  • 1、先导jar
     示例:
    <dependencies>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.2.3.RELEASEversion>
        dependency>
        
        <dependency>
            <groupId>cglibgroupId>
            <artifactId>cglibartifactId>
            <version>3.3.0version>
        dependency>
        
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.9.5version>
        dependency>
        
        <dependency>
            <groupId>aopalliancegroupId>
            <artifactId>aopallianceartifactId>
            <version>1.0version>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-aspectsartifactId>
            <version>5.2.3.RELEASEversion>
        dependency>
    dependencies>
  • 2、xml文件中引入aop命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   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/aop
   http://www.springframework.org/schema/aop/spring-aop.xsd">

 即上述代码中的第三行和最后两行。

  • 3、开启aop注解扫描
     如下:
       
       <context:component-scan base-package="com.bie.aop">context:component-scan>   
         
           
       <aop:aspectj-autoproxy>aop:aspectj-autoproxy>
  • 4、目标方法
     假设有如下的目标方法:
package com.bie.aop;
public interface IUserDao {
     
    public void save();
}
package com.bie.aop;
import org.springframework.stereotype.Component;
/**
* 目标对象
*/

@Component
public class UserDao implements IUserDao{
     
    @Override
    public void save() {
     
        System.out.println("..核心业务--核心业务..");
    }
}
  • 5、实现切面类
package com.bie.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;


/**
* @Aspect:指定当前类为切面类
*/
@Component  //加入到IoC容器
@Aspect  //指定当前类为切面类
public class Aop {
     

    //指定切入点表达式,拦截那些方法,即为那些类生成代理对象
    //@Pointcut("execution(* com.bie.aop.UserDao.save(..))")  ..代表所有参数
    //@Pointcut("execution(* com.bie.aop.UserDao.*())")  指定所有的方法
    //@Pointcut("execution(* com.bie.aop.UserDao.save())") 指定save方法

    @Pointcut("execution(* com.bie.aop.UserDao.*(..))")
    public void pointCut(){
     

    }

    @Before("pointCut()")
    public void begin(){
     
        System.out.println("开启事务");
    }

    @After("pointCut()")
    public void close(){
     
        System.out.println("关闭事务");
    }
}

你可能感兴趣的:(Java后端开发知识点,spring,面试,java)