一:Spring 框架中都用到了哪些设计模式?
工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller
二:详细讲解一下核心容器(spring context 应用上下文) 模块
这是基本的 Spring 模块,提供 spring 框架的基础功能,BeanFactory 是 任何以 spring
为基础的应用的核心。Spring 框架建立在此模块之上,它使 Spring 成为一个容器。
Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正
的应用代码中分离。最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据 XML 文件中的定义加载 beans。该容器从 XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。
三:Spring 框架中有哪些不同类型的事件
Spring 提供了以下 5 种标准的事件:
1. 上下文更新事件(ContextRefreshedEvent):
在调用ConfigurableApplicationContext 接口中的 refresh()方法时被触发。
2. 上下文开始事件(ContextStartedEvent):
当容器调用ConfigurableApplicationContext 的 Start()方法开始/重新开始容器时触发该事件。
3. 上下文停止事件(ContextStoppedEvent):
当容器调用ConfigurableApplicationContext 的 Stop()方法停止容器时触发该事件。
4. 上下文关闭事件(ContextClosedEvent):
当 ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁。
5. 请求处理事件(RequestHandledEvent):
在 Web 应用中,当一个 http 请求(request)结束触发该事件。如果一个 bean 实现了 ApplicationListener 接口,当一个ApplicationEvent 被发布以后,bean 会自动被通知。
四:Spring 应用程序有哪些不同组件?
Spring 应用一般有以下组件:
1、接口 - 定义功能。
2、Bean 类 - 它包含属性,setter 和 getter 方法,函数等。
3、Bean 配置文件 - 包含类的信息以及如何配置它们。
4、Spring 面向切面编程(AOP) - 提供面向切面编程的功能。
5、用户程序 - 它使用接口
五:Spring 控制反转(IOC)
什么是 Spring IOC 容器?
控制反转即 IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
控制反转(IoC)有什么作用
1、管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的
2、解耦,由容器去维护具体的对象
3、托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的
IOC 的优点是什么?
1、IOC 或 依赖注入把应用的代码量降到最低。
2、它使应用容易测试,单元测试不再需要单例和 JNDI 查找机制。
3、最小的代价和最小的侵入性使松散耦合得以实现。
4、IOC 容器支持加载服务时的饿汉式初始化和懒加载。
Spring IoC 的实现机制
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
Spring 的 IoC 支持哪些功能
Spring 的 IoC 设计支持以下功能:
1、依赖注入
2、依赖检查
3、自动装配
4、支持集合
5、指定初始化方法和销毁方法
6、支持回调某些方法(但是需要实现 Spring 接口,略有侵入)
其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring
RuntimeBeanReference 对象。
对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依
赖注入。
六:BeanFactory 和 ApplicationContext 有什么区别?
BeanFactory 和 ApplicationContext 是 Spring 的两大核心接口,都可以当做 Spring 的容器。其中 ApplicationContext 是 BeanFactory 的子接口。
依赖关系
BeanFactory:是 Spring 里面最底层的接口,包含了各种 Bean 的定义,读取 bean 配置
文档,管理 bean 的加载、实例化,控制 bean 的生命周期,维护 bean 之间的依赖关系。
ApplicationContext 接口作为 BeanFactory 的派生,除了提供 BeanFactory 所具有的功
能外,还提供了更完整的框架功能:
继承 MessageSource,因此支持国际化。
1、统一的资源文件访问方式。
2、提供在监听器中注册 bean 的事件
3、同时加载多个配置文件。
4、载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的 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 则是自动注册。
七: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 关系。
Spring中 容器是如何实现IOC的
- 加载配置文件,解析成 BeanDefinition 放在 Map 里。
- 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
上面就是 Spring 低级容器(BeanFactory)的 IoC。
至于高级容器 ApplicationContext,他包含了低级容器的功能,当他执行 refresh 模板方
法的时候,将刷新整个容器的 Bean。同时其作为高级容器,包含了太多的功能。一句话,
他不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持
访问文件资源,支持事件发布通知,支持接口回调等等。
ApplicationContext 通常的实现是什么?
FileSystemXmlApplicationContext :
此容器从一个 XML 文件中加载 beans 的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
ClassPathXmlApplicationContext:
此容器也从一个 XML 文件中加载 beans 的定义,这里,你需要正确设置 classpath 因为这个容器将在 classpath 里找 bean 配置。
WebXmlApplicationContext:
此容器加载一个 XML 文件,此文件定义了一个 WEB 应用的所有 bean。
八:关于 Spring 的bean对象
Spring 中的 bean 的作用域有哪些?
singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
prototype : 每次请求都会创建一个新的 bean 实例。
request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
Spring 中的单例 bean 的线程安全问题了解吗?
的确是存在安全问题的。因为,当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。
但是,一般情况下,我们常用的 Controller、Service、Dao 这些 Bean 是无状态的。无状态的 Bean 不能保存数据,因此是线程安全的。
- 有状态就是有数据存储功能。
- 无状态就是不会保存数据。
常见的有 2 种解决办法:
在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
改变 Bean 的作用域为 “prototype”:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题
解释 Spring 框架中 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 声明了销毁方法,该方法也会被调用
哪些是重要的 bean 生命周期方法? 你能重载它们吗?
有两个重要的 bean 生命周期方法,第一个是 setup , 它是在容器加载 bean 的时候被调
用。第二个方法是 teardown 它是在容器卸载类的时候被调用。
bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们你可以自己定
制初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)。
什么是 Spring 的内部 bean?什么是 Spring inner beans?
在 Spring 框架中,当一个 bean 仅被用作另一个 bean 的属性时,它能被声明为一个内部
bean。内部 bean 可以用 setter 注入“属性”和构造方法注入“构造参数”的方式来实现,
内部 bean 通常是匿名的,它们的 Scope 一般是 prototype
什么是 bean 装配?
装配,或 bean 装配是指在 Spring 容器中把 bean 组装到一起,前提是容器需要知道 bean
的依赖关系,如何通过依赖注入来把它们装配到一起
什么是 bean 的自动装配?
在 Spring 框架中,在配置文件中设定 bean 的依赖关系是一个很好的机制,Spring 容器
能够自动装配相互合作的 bean,这意味着容器不需要和配置,能通过 Bean 工厂自动处理
bean 之间的协作。这意味着 Spring 可以通过向 Bean Factory 中注入的方式自动搞定
bean 之间的依赖关系。自动装配可以设置在每个 bean 上,也可以设定在特定的 bean 上。
解释不同方式的自动装配,spring 自动装配 bean 有哪些方式?
在 spring 中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作
的对象引用赋予各个对象,使用 autowire 来配置自动装载模式。
在 Spring 框架 xml 配置中共有 5 种自动装配:
- no:默认的方式是不进行自动装配的,通过手工设置 ref 属性来进行装配 bean。
- byName:通过 bean 的名称进行自动装配,如果一个 bean 的 property 与另一
bean 的 name 相同,就进行自动装配。 - byType:通过参数的数据类型进行自动装配。
- constructor:利用构造函数进行装配,并且构造函数的参数通过 byType 进行装配。
- autodetect:自动探测,如果有构造方法,通过 construct 的方式自动装配,否则
使用 byType 的方式自动装配。
使用@Autowired 注解自动装配的过程是怎样的?
使用@Autowired 注解来自动装配指定的 bean。在使用@Autowired 注解之前需要在
Spring 配置文件进行配置,<context:annotation-config />。
在启动 spring IoC 时,容器自动装载了一个 AutowiredAnnotationBeanPostProcessor
后置处理器,当容器扫描到@Autowied、@Resource 或@Inject 时,就会在 IoC 容器自
动查找需要的 bean,并装配给该对象的属性。在使用@Autowired 时,首先在容器中查询
对应类型的 bean:
- 如果查询结果刚好为一个,就将该 bean 装配给@Autowired 指定的数据;
- 如果查询的结果不止一个,那么@Autowired 会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用 required=false。
自动装配有哪些局限性?
自动装配的局限性是:
重写:你仍需用 和 配置来定义依赖,意味着总要重写自动装配。
基本数据类型:你不能自动装配简单的属性,如基本数据类型,String 字符串,和类。
模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。
可以在 Spring 中注入一个 null 和一个空字符串吗?
可以
九:关于Spring注解配置
怎样开启注解装配?
注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在 Spring 配置文件中配
置<context:annotation-config/>
元素。
@Component, @Controller, @Repository, @Service 有何区别?
@Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。
spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
@Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。
@Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。
您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。
@Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。
它为DAO 提供了额外的好处。
它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。
@Required 注解有什么作用
这个注解表明 bean 的属性必须在配置的时候设置,通过一个 bean 定义的显式的属性值或
通过自动装配,若@Required 注解的 bean 属性未被设置,容器将抛出BeanInitializationException
@Autowired 注解有什么作用
@Autowired 默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设
置它 required 属性为 false)。@Autowired 注解提供了更细粒度的控制,包括在何处以
及如何完成自动装配。它的用法和@Required 一样,修饰 setter 方法、构造器、属性或者
具有任意名称和/或多个参数的 PN 方法。
@Autowired 和@Resource 之间的区别
@Autowired 可用于:构造函数、成员变量、Setter 方法
@Autowired 和@Resource 之间的区别
- @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可
以设置它 required 属性为 false)。 - @Resource 默认是按照名称来装配注入的,只有当找不到与名称匹配的 bean 才会
按照类型来装配注入。
@Qualifier 注解有什么作用
当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,您可以使用
@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。
@RequestMapping 注解有什么用?
@RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中
的特定类/方法。此注释可应用于两个级别:
- 类级别:映射请求的 URL
- 方法级别:映射 URL 以及 HTTP 请求方法
@Component 和 @Bean 的区别是什么?
1、 作用对象不同: @Component 注解作用于类,而@Bean注解作用于方法。
2、@Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
3、@Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
十:关于Spring的事务
Spring 支持的事务管理类型, spring 事务实现方式有哪些?
Spring 支持两种类型的事务管理:
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和 XML 配置
来管理事务。
Spring 事务的实现方式和实现原理
Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring 是无法
提供事务功能的。真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实现的。
说一下 Spring 的事务传播行为
spring 事务的传播行为说的是,当多个事务同时存在的时候,spring 如何处理这些事务的
行为。
① PROPAGATION_REQUIRED:
如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:
支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,
就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 REQUIRED 属性执行。
说一下 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 条记录,这就好像产生了幻觉。发生
幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一
个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。
十一:Spring 面向切面编程(AOP)
什么是 AOP
OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并
适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补
充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一
个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低
了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?
AOP 实现的关键在于 代理模式,AOP 代理主要分为静态代理和动态代理。静态代理的代
表为 AspectJ;动态代理则以 Spring AOP 为代表。
(1)AspectJ 是静态代理的增强,所谓静态代理,就是 AOP 框架会在编译阶段生成 AOP
代理类,因此也称为编译时增强,他会在编译阶段将 AspectJ(切面)织入到 Java 字节码中,
运行的时候就是增强之后的 AOP 对象。
(2)Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,
而是每次运行时在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象
的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
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 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
如何理解 Spring 中的代理?
将 Advice 应用于目标对象后创建的对象称为代理。在客户端对象的情况下,目标对象和
代理对象是相同的。
Advice + Target Object = Proxy
解释一下 Spring AOP 里面的几个名词
(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 就是以这种方式织入切面。
Spring 在运行时通知对象
通过在代理类中包裹切面,Spring 在运行期把切面织入到 Spring 管理的 bean 中。代理封
装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标 bean。当代理拦截到
方法调用时,在调用目标 bean 方法之前,会执行切面逻辑。
直到应用需要被代理的 bean 时,Spring 才创建代理对象。如果使用的是
ApplicationContext 的话,在 ApplicationContext 从 BeanFactory 中加载所有 bean 的
时候,Spring 才会创建被代理的对象。因为 Spring 运行时才创建代理对象,所以我们不需
要特殊的编译器来织入 SpringAOP 的切面。
Spring 只支持方法级别的连接点
因为 Spring 基于动态代理,所以 Spring 只支持方法连接点。Spring 缺少对字段连接点的
支持,而且它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用 Aspect 来
补充。
在 Spring AOP 中,关注点和横切关注的区别是什么?在 spring aop 中 concern 和 cross-cutting concern 的不同之处
关注点(concern)是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现
的一个功能。
横切关注点(cross-cutting concern)是一个关注点,此关注点是整个应用都会使用的功
能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因
此这些都属于横切关注点。
Spring 通知有哪些类型?
在 AOP 术语中,切面的工作被称为通知,实际上是程序执行时要通过 SpringAOP 框架触
发的代码段。
Spring 切面可以应用 5 种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什
么; - 返回通知(After-returning ):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用
之后执行自定义的行为。
同一个 aspect,不同 advice 的执行顺序:
①没有异常情况下的执行顺序:
around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
②有异常情况下的执行顺序:
around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生
什么是切面 Aspect?
aspect 由 pointcount 和 advice 组成,切面是通知和切点的结合。 它既包含了横切逻
辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定
义的横切逻辑编织到切面所指定的连接点中. AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:
- 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上
- 如何在 advice 中编写切面代码.
可以简单地认为, 使用 @Aspect 注解的类就是切面.