什么是Spring
Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题,以提高开发效率。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。
Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发,提高下发效率。
为什么要使用Spring
根据Spring官网所述,Spring可以实现更快,更轻松,更安全地编程。 Spring对速度,简单性和生产力的关注使其成为世界上最受欢迎的Java 开发框架。具体来说,主要体现在以下六个方面:
(1) Spring无处不在。Spring受到全世界开发人员的信任。各个领域,各个企业都在广泛使用。
(2) Spring灵活性强。Spring提供的扩展能力和启动的粘合第三方库的能力,可以使开发人员轻松构建几乎任何可以想象的应用程序。Spring框架的控制反转(IoC)能力,为广泛的功能集奠定了基础。
(3) Spring可以提高生产力。Spring Boot改变了Java编程的方式,从根本上简化了编程体验。Spring Cloud使微服务开发变得轻松。
(4) Spring快速。Spring可以快速启动,快速关闭并支持执行的优化。Spring项目也越来越多地支持反应式(非阻塞)编程模型,以提高效率。
(5) Spring注重安全。Spring代码提交者与安全专家合作,修补和测试所有报告的漏洞。Spring密切关注关联的第三方类库,并定期发布更新以帮助确保数据和应用程序尽可能安全。此外,Spring Security可以更轻松地与行业标准安全方案集成。
(6) Spring社区支持较好。Spring社区是一个庞大,全球化,多元的社区,涵盖了从初学者到经验丰富的职业人士的各个阶段需要内容,拥有带入新高度所需的支持和资源:快速入门,指南和教程,视频,聚会,支持等等。
Spring 框架简介
(1) Spring Core:提供 Spring 框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
(2) Spring AOP:提供 Spring 框架面向切面的编程能力:将面向切面的编程功能集成到 Spring 框架中,可以将 Spring 框架管理的任何对象实现 AOP;提供了事务管理服务:通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
(3) Spring Context:向 Spring 框架提供上下文信息。Spring 上下文支持消息源和观察者模式。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
(4) Spring ORM:Spring 框架整合了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
(5) Spring DAO:提供数据访问能力(访问数据库);提供了有意义的异常层次结构:可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。
(6) Spring Web:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成;Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
(7) Spring MVC:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
Spring Bean
Bean创建的容器类
Spring 有两个容器类: BeanFactory 和 ApplicationContext。
BeanFactory:这是一个最简单的容器,它主要的功能是为依赖注入(DI)提供支持。
ApplicationContext:Application Context 是 Spring 中的高级容器。和 BeanFactory 类似,它可以加载和管理配置文件中定义的 Bean。 另外,它还增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。
BeanFactory 和 ApplicationContext 的区别?
二者都是 Spring 框架的两大核心接口,都可以当做 Spring 的容器。其中 ApplicationContext 是 BeanFactory 的子接口。
BeanFactory 是 Spring 里面最底层的接口,包含了各种 Bean 的定义,读取配置文档,管理 Bean 的加载、实例化,控制 Bean 的生命周期,维护对象之间的依赖关系等功能。
ApplicationContext 接口作为 BeanFactory 的派生,除了提供 BeanFactory 所具有的功能外,还提供了更完整的框架功能:
(1) 继承 MessageSource,支持国际化。
(2) 统一的资源文件访问方式。
(3) 提供在监听器中注册 Bean 的事件。
(4) 支持同时加载多个配置文件。
(5) 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,如应用的 Web 层。
具体区别体现在以下三个方面:
(a) 加载方式不同
BeanFactroy 采用的懒加载方式注入 Bean,即只有在使用到某个 Bean 时才对该 Bean 实例化。这样,我们就不能在程序启动时发现一些存在的 Spring 的配置问题。
ApplicationContext 是在启动时一次性创建了所有的 Bean。
(b) 创建方式不同
BeanFactory 通常以编程的方式被创建,ApplicationContext 还能以声明的方式创建,如使用 ContextLoader。
(c) 注册方式不同
二者都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但 BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。
Bean的创建过程
(1) Spring Bean的容器类通过解析注解类或者以其他方式定义的类,将Bean标签解析成BeanDefinition(Bean定义信息)
(2) BeanFactory通过获取到的BeanDefinition,利用反射创建Bean对象。
(3) 通过populateBean()方法对Bean对象进行属性填充。
(4) 通过invokeAwareMethods()方法对Bean对象进行赋值。
(5) 调用BeanPostProcessor的初始化前置方法。
(6) 调用init-method方法,进行初始化操作。
(7) 调用BeanPostProcessor的初始化后置方法(AOP在此处进行)。
(8) 将创建好的Bean对象放入BeanDefinitionMap容器中。
(9) 通过Context.getBean()方法获得Bean对象并使用。–正常是通过依赖注入的方式使用
(10) spring容器关闭时会调用DisposableBean的destory()方法销毁Bean对象(如果配置了destory-method属性,spring会自动调用指定的销毁方法)。
单例bean的初始化以及依赖注入一般都在容器初始化阶段进行,只有懒加载(lazy-init为true)的单例bean是在应用第一次调用getBean()时进行初始化和依赖注入。
多例bean 在容器启动时不实例化,即使设置 lazy-init 为 false 也没用,只有调用了getBean()才进行实例化。
Spring Bean 作用域
Spring 提供以下五种 Bean 的作用域:
(1) Singleton: Bean 在每个 Spring Ioc 容器中只有一个实例,也是 Spring 的默认配置。
(2) Prototype:一个 Bean 的定义可以有多个实例。
(3) Request:每次 Http 请求都会创建一个 Bean,故该作用域仅在基于 Web 的 Spring ApplicationContext情形下有效。
(4) Session:在一个 Http Session 中,一个 Bean 对应一个实例。该作用域同样仅在基于 Web 的 Spring ApplicationContext 情形下有效。
(5) Global-session:在一个全局的 Http Session 中,一个 Bean 定义对应一个实例。
值的注意的是:使用 Prototype 作用域时需要慎重的思考,因为频繁创建和销毁 Bean 会带来很大的性能开销。
Spring 自动装配
Spring的自动装配有三种模式:byType(根据类型),byName(根据名称)、constructor(根据构造函数)。
@Autowired是按类型匹配的(byType),如果需要按名称(byName)匹配的话,可以使用@Qualifier注解与@Autowired结合。
@Resource,默认按 byName模式自动注入。@Resource有两个中重要的属性:name和type。Spring容器对于@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。
上述两种自动装配的依赖注入并不适合简单值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的方式。@Value接收一个String的值,该值指定了将要被注入到内置的java类型属性值,Spring 容器会做好类型转换。一般情况下@Value会与properties文件结合使用。
Spring 的单例是否线程安全?
Spring 中的单例 Bean 并不是线程安全的。
但我们日常使用时往往并未做多线程并发处理,那又是如何保证线程安全的呢?
实际上大部分时候我们定义的 Bean 是无状态的(如 dao 类),所有某种程度上来说 Bean 也是安全的,但如果 Bean 有状态的话(比如 model 对象),那就要开发者自己去保证线程安全了。
其中有状态就是有数据存储功能,无状态就是不会。
对于有状态的Bean,如何保证线程安全
使用ThreadLocal来保证。也可使用多例的模式,但是不推荐(在高并发场景下)。
Spring Bean 生命周期
Bean 在 Spring 容器中从创建到销毁经历了若干阶段,每一阶段都可以进行个性化定制。
(1)实例化:Spring 对 Bean 进行实例化;
(2)属性注入:Spring 将配置和 Bean 的引用注入到对应的属性中;
(3)如果 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的 ID 传递给 setBeanName() 方法;
(4)如果 Bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法将 BeanFactory 容器实例传入;
(5)如果 Bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 方法将 Bean 所在的应用上下文的引用传入进来;
(6)BeanPostProcessor前置处理:如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessBeforeInitialization() 方法;
(7)初始化:如果 Bean 实现了 InitializingBean 接口,Spring 将调用它们的 afterPropertiesSet() 方法。类似地,如果 Bean 使用 initmethod 声明了初始化方法,该方法也会被调用;
(8)BeanPostProcessor后置处理:如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的postProcessAfterInitialization()方法;
(9)使用:此时,Bean 已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
(10)销毁:如果 Bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy() 接口方法。同样,如果使用 destroymethod 声明了销毁方法,该方法也会被调用。
Spring 常用的Bean注解
(1) @Configuration、@Component、@Service、@Repository、@Controller:用于标识Bean的角色。
(2) @Autowired、@Qualifier、@Resource、@Value:用于自动装配Bean。
(3) @PostConstruct、@PreDestroy:用于指定初始化和销毁方法。
(4) @Transactional:用于声明式事务管理。
(5) @Scope:用于指定Bean作用域。
(6) @Async:用于实现异步编程。
@Bean和@Component有什么区别?
都是使用注解定义 Bean。@Bean 是使用 Java 代码装配 Bean,@Component 是自动装配 Bean。
@Component 注解用在类上,表明一个类会作为组件类,并告知Spring要为这个类创建bean,每个类对应一个 Bean。
@Bean 注解用在方法上,表示这个方法会返回一个 Bean。@Bean 需要在配置类中使用,即类上需要加上@Configuration注解。
@Bean 注解更加灵活。当需要将第三方类装配到 Spring 容器中,因为没办法源代码上添加@Component注解,只能使用@Bean 注解的方式。
Spring Bean 循环依赖及解决办法
循环依赖指两个或多个Bean之间相互引用,形成了一个无限循环调用的情况。在Spring容器中,如果存在循环依赖,但是都是单例模式的Bean,则可以通过Spring容器提前暴露正在创建的Bean,从而避免循环依赖问题。如果存在循环依赖且其中一方是原型模式的Bean,则Spring无法处理这种情况。
临时规避:显式配置参数。
循环依赖问题在Spring中主要有三种情况:
(1)通过构造方法进行依赖注入时产生的循环依赖问题。
(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:
第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。
解释一下 Spring IoC
IoC简介
IoC(Inversion of Control,即"控制反转"),不是一种技术,而是一种设计思想。传统程序设计,直接在对象内部通过new的方式创建对象。而这种方式会导致程序间的耦合。假如类A依赖于接口IB,且在其内部创建了类B(实现接口IB)的一个实例。这种方式会将类B的实例的创建耦合到了类A中,当需要使用接口IB的另一个实现时,就需要更改代码。而IoC则反转依赖对象的获取方式。IoC提供一个容器来创建这些对象,即由Ioc容器来控制对象的创建。这样,当类A需要依赖接口IB时,仅需声明对IB的引用,具体的实例化由IoC容器负责。
DI简介
DI(Dependency Injection,依赖注入),就是让容器去决定依赖关系,即容器全权负责的组件的装配,它会把符合依赖关系的对象通过属性或者构造函数传递给需要的对象。常见的依赖注入实现有三种:构造方法注入、setter方法注入、接口注入(从 Spring4 开始已被废弃)等。
(1) 构造方法注入:即被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象,然后外部会检查被注入对象的构造方法,取得其所需要的依赖对象列表,进而为其注入相应对象。
(2) setter方法注入:即当前对象只需要为其依赖对象所对应的属性添加setter方法,IoC容器通过此setter方法将相应的依赖对象设置到被注入对象的方式即setter方法注入。
(3) 接口注入:被注入对象如果想要IoC容器为其注入依赖对象,就必须实现某个接口,这个接口提供一个方法,用来为被注入对象注入依赖对象,IoC容器通过接口方法将依赖对象注入到被注入对象中去。相对于前两种注入方式,接口注入比繁琐和死板,被注入对象必须声明和实现另外的接口。
解释一下AOP
AOP(Aspect-Oriented Programming,面向切面编程),可以看成OOP(Object-Oriented Programing,面向对象编程)的补充和完善。面向对象编程时,建立了一种对象层次结构,可以定义从上到下的关系,但不适合定义从左到右的关系。如日志功能。日志代码水平地散布在所有的对象层次中,也就是说,多个对象,使用相似或相同的日志代码。对于其他类型的代码,如安全性、异常处理等也是如此。这种散布在各个对象中的业务逻辑无关的代码被称为横切(cross-cutting)代码,在面向对象设计中,这种代码导致了大量的代码重复,不利于模块的重用。
简单来说,面向对象编程的自上而下的关系,不适合自左向右的关系,会带来横切代码的重复。而AOP技术主要针对这个问题进行优化。
AOP技术可以将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”(即切面)。所谓“切面”,就是将那些与业务无关但业务模块所共同调用的逻辑或责任封装起来,从而减少系统的重复代码,降低模块间的耦合度。
AOP把软件系统分为两个部分:核心关注点和横切关注点。核心关注点主要指业务处理的主要流程,横切关注点则指与业务处理的主要流程关系不大的部分。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
(1) 切面(Aspect):切面是通知和切入点的结合。通知和切入点共同定义了切面的全部内容。
(2) 连接点(Join point):指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
(3) 通知(Advice):在AOP术语中,切面的工作被称为通知。
(4) 切入点(Pointcut):切入点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点,最经典的使用方式是使用注解。
(5) 引入(Introduction):引入允许我们向现有类添加新方法或属性。
(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。
(7) 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有以下时间点可以进行织入:
(a) 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
(b) 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
(c) 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面。
Spring AOP应用场景
AOP常见于以下场景:
(1) 日志记录
(2) 权限校验和管理
(3) 缓存
(4) 事务
Spring AOP 实现原理
Spring AOP是基于动态代理实现。通过动态代理,可以对被代理对象的方法进行增强。Spring AOP用到了两种动态代理技术:JDK动态代理、CGLIB库。
JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB(Code Generation Library) 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。
Spring AOP默认策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。
更多动态代理的介绍可参考笔者之前的文章。
Spring AOP 使用方式
Spring Boot简化了AOP的使用。这里仅介绍如何在Spring Boot中使用AOP。
(1) 引入Maven依赖
<!--引用AOP注解功能开始-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--引用AOP注解功能结束-->
(2) 自定义注解
/**
* @Author: courage007
* @Date: 2021/02/05/下午3:54
* @Description:
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomAnnotation {
/**
*
* @return 指定值
*/
String value() default "";
/**
*
* @return 指定名称
*/
String name() default "";
}
(3) 自定义切面并声明切入点并实现AOP通知
/**
* @Author: courage007
* @Date: 2021/02/05/下午3:47
* @Description:
*/
@Aspect
@Component
public class CustomAspect implements Ordered {
/**
* * 切入点
*/
@Pointcut("@annotation(com.github.courage007.springdemo.annotation.CustomAnnotation)")
public void pointCutCustomAnnotation() {
// 无需内容
System.out.println("Test Pointcut Method");
}
@Before("pointCutCustomAnnotation()")
public void before(JoinPoint joinPoint) {
try {
Signature signature = joinPoint.getSignature();
//获取方法参数
Object[] arguments = joinPoint.getArgs();
//获取目标类
Class<?> aClass = joinPoint.getTarget().getClass();
//获取类名
String targetName = aClass.getName();
//获取当前方法名insert
String methodName = signature.getName();
//获取方法数组
Method[] methods = aClass.getMethods();
String operation = "";
//获取当前方法1
Class<?>[] argTypes = new Class[arguments.length];
for (int i = 0; i < arguments.length; i++) {
argTypes[i] = arguments[i] == null ? null : arguments[i].getClass();
}
Method method = null;
try {
method = aClass.getMethod(methodName, argTypes);
operation = method.getAnnotation(CustomAnnotation.class).value();
} catch (NoSuchMethodException e) {
}
//获取当前方法2
MethodSignature signature1 = (MethodSignature)joinPoint.getSignature();
Method method1 = signature1.getMethod();
//获取方法参数类型数组
Class<?>[] parameterTypes = method1.getParameterTypes();
//获取当前方法3
for (Method mm : methods) {
if (mm.getName().equals(methodName)) {
Class<?>[] clazzs = mm.getParameterTypes();
if (clazzs.length == arguments.length) {
//获取代理方法上注解的operation参数的值
operation = mm.getAnnotation(CustomAnnotation.class).value();
break;
}
}
}
//通过方法获取注解参数值
CustomAnnotation serviceLog= method.getAnnotation(CustomAnnotation.class);
String item= serviceLog.value();
String biz = serviceLog.name();
StringBuilder paramsBuf = new StringBuilder();
for (Object arg : arguments) {
paramsBuf.append(arg);
paramsBuf.append("&");
}
} catch (Throwable e) {
}
}
@After("pointCutCustomAnnotation()")
public void after(JoinPoint joinPoint) {
try {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class<?> targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String operation = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class<?>[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
operation = method.getAnnotation(CustomAnnotation.class).value();
break;
}
}
}
StringBuilder paramsBuf = new StringBuilder();
for (Object arg : arguments) {
paramsBuf.append(arg);
paramsBuf.append("&");
}
} catch (Throwable e) {
}
}
/**
* 环绕通知处理处理
*
* @param
* @throws Throwable
*/
@Around("pointCutCustomAnnotation()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 先执行业务,注意:业务这样写业务发生异常不会拦截日志。
Object result = point.proceed();
try {
handleAround(point);// 处理日志
} catch (Exception e) {
}
return result;
}
@AfterReturning(pointcut = "pointCutCustomAnnotation()")
public void doAfterReturning(JoinPoint joinPoint) {
System.out.println("TTEESSTT");
}
/**
* around日志记录
*
* @param point
* @throws SecurityException
* @throws NoSuchMethodException
*/
public void handleAround(ProceedingJoinPoint point) throws Exception {
Signature sig = point.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
// 方法名称
String methodName = currentMethod.getName();
// 获取注解对象
CustomAnnotation aLog = currentMethod.getAnnotation(CustomAnnotation.class);
// 类名
String className = point.getTarget().getClass().getName();
// 方法的参数
Object[] params = point.getArgs();
StringBuilder paramsBuf = new StringBuilder();
for (Object arg : params) {
paramsBuf.append(arg);
paramsBuf.append("&");
}
}
@AfterThrowing(pointcut = "pointCutCustomAnnotation()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
try {
String targetName = joinPoint.getTarget().getClass().getName();
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class<?> targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String operation = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class<?>[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
operation = method.getAnnotation(CustomAnnotation.class).value();
break;
}
}
}
StringBuilder paramsBuf = new StringBuilder();
for (Object arg : arguments) {
paramsBuf.append(arg);
paramsBuf.append("&");
}
} catch (Exception ex) {
}
}
}
(4) 使用自定义注解
/**
* @Author: courage007
* @Date: 2021/02/05/下午3:15
*/
public class CustomController {
@Autowired
private ICustomService customService;
@GetMapping("/xxx")
@CustomAnnotation(value = "测试", name = "test")
public Integer getXXX() {
return customService.getXXX();
}
}
Spring AOP通知顺序
AOP支持的通知类型有:Before、After、Around、AfterReturning、AfterThrowing等。接下来详细说明下:
(1) Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可。
(2) AfterReturning:在目标方法正常完成之后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值。
(3) AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象。
(4) After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式。
(5) Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务、日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint。
注意,spring aop在方法正常返回和抛出异常时,各个通知的执行顺序不同。此外,Spring版本不一样,通知执行顺序也会存在差异。
(1) Spring4.0
正常情况:环绕前置=====@Before目标方法执行=环绕返回=环绕最终===@After=====@AfterReturning
异常情况:环绕前置=====@Before目标方法执行=环绕异常=环绕最终===@After=====@AfterThrowing
(2) Spring5.28
正常情况:环绕前置=====@Before=目标方法执行=@AfterReturning=====@After=环绕返回=环绕最终
异常情况:环绕前置=====@Before=目标方法执行=@AfterThrowing=====@After=环绕异常=环绕最终
可见,Spring 5 提前了@AfterXXX的执行位置。
多个切面的执行顺序
上面的例子仅描述单个切面的执行顺序,如果在同一个方法有多个AOP,其执行不会存在任何顺序。也就是说,这些代码会随机生成。如果需要按照指定的顺序运行,还需手动设置优先级。
Spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfterReturn、doAfter方法。也就是说对多个AOP来说,先before的,一定后after。
配置多个切面的执行顺序的方式
Spring支持多种方式配置AOP执行顺序,如实现实现org.springframework.core.Ordered接口、使用@Ordered注解、配置文件添加配置等。
(1) 实现org.springframework.core.Ordered接口
@Aspect
@Component
public class CustomAspectWithOrder implements Ordered {
@Override
public int getOrder() {
return 1;
}
}
(2) 使用@Ordered注解
@Aspect
@Component
@Order(1)
public class CustomAspectWithOrder {
}
(3) 配置文件添加配置
<aop:config expose-proxy="true">
<aop:aspect ref="aopBean" order="0">
<aop:pointcut id="testPointcut" expression="@annotation(xxx.xxx.xxx.annotation.xxx)"/>
<aop:around pointcut-ref="testPointcut" method="doAround" />
</aop:aspect>
</aop:config>
@Pointcut注解
@Pointcut注解用于声明切入点。在@Pointcut注解中,通过书写切入点表达式来描述需要处理的连接点集合。@Pointcut注解定义如下:
/**
* Pointcut declaration
*
* @author Alexandre Vasseur
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
/**
* @return the pointcut expression
* We allow "" as default for abstract pointcut
*/
String value() default "";
/**
* When compiling without debug info, or when interpreting pointcuts at runtime,
* the names of any arguments used in the pointcut are not available.
* Under these circumstances only, it is necessary to provide the arg names in
* the annotation - these MUST duplicate the names used in the annotated method.
* Format is a simple comma-separated list.
*
* @return argNames the argument names (should match those in the annotated method)
*/
String argNames() default "";
}
@DeclareParents注解
在面向切面的编程中,对方法的增强叫做Wearing(织入),而对类的增强叫introduction(引入)。Introduction Advice(引入增强)就是对已有类添加方法,它也是面向切面编程提供的一种特殊增强。
在Spring AOP中,使用@DeclareParents注解实现引入增强。
(1) 定义增强方法及对应类
/**
* @Author: courage007
* @Date: 2021/02/05/下午8:49
* @Description:
*/
public interface ICustomServiceProxy {
Integer getYYY();
}
/**
* @Author: courage007
* @Date: 2021/02/05/下午8:50
* @Description:
*/
public class CustomServiceProxyImpl implements ICustomServiceProxy {
@Override
public Integer getYYY() {
return 222;
}
}
(2) 切面中声明@DeclareParents注解
/**
* @Author: courage007
* @Date: 2021/02/05/下午3:47
* @Description:
*/
@Order(1)
@Aspect
@Component
public class CustomAspect {
@DeclareParents(value = "com.github.courage007.springdemo.service.impl.CustomService*", defaultImpl = CustomServiceProxyImpl.class)
private ICustomServiceProxy customServiceProxy;
}
(3) 使用引入增强的方法
/**
* @Author: courage007
* @Date: 2021/02/05/下午3:15
* @Description:
*/
@RestController
@RequestMapping("custom")
public class CustomController {
@Autowired
private ICustomService customService;
@GetMapping("/xxx")
@CustomAnnotation(value = "测试", name = "test")
public Integer getXXX() {
// 使用强制类型转换
ICustomServiceProxy customServiceProxy = (ICustomServiceProxy) customService;
customServiceProxy.getYYY();
return customService.getXXX();
}
}
Spring 事务
Spring事务的本质就是数据库对事务的支持。Spring框架提供统一的事务抽象,通过统一的编程模型使得应用程序可以很容易地在不同的事务框架之间进行切换。
无论是JTA、JDBC、Hibernate/JPA、Mybatis/Mybatis-Plus,Spring都使用统一的编程模型,使得应用程序可以很容易地在不同的事务框架之间进行切换。这也符合面向接口编程思想。Spring事务框架的代码在org.springframework:spring-tx中。Spring事务抽象的核心类图如下:
Spring事务管理的核心接口是PlatformTransactionManager。接口PlatformTransactionManager定义事务操作的行为,PlatformTransactionManager依赖TransactionDefinition和TransactionStatus接口。TransactionDefinition接口定义与Spring兼容的事务属性(如隔离级别、事务传播行为等)。TransactionStatus接口则定义事务的状态(如是否回滚、是否完成、是否包含安全点(Save Point)、将基础会话刷新到数据存储区(如果适用)等)。
PlatformTransactionManager简介
PlatformTransactionManager是Spring事务框架的核心接口。应用程序可以直接使用PlatformTransactionManager,但它并不是主要用于API:应用程序将借助事务模板(TransactionTemplate)或声明式事务(Declarative Transaction)。
对于需要实现PlatformTransactionManager接口的应用程序,可通过继承AbstractPlatformTransactionManager抽象类的方式实现。AbstractPlatformTransactionManager类已实现事务传播行为和事务同步处理。子类需要实现针对事务特定状态(如:begin,suspend,resume,commit)的模板方法。Spring事务框架已经实现了JtaTransactionManager(JPA)和DataSourceTransactionManager(JDBC)。应用程序可以参考以上方法实现事务管理器。PlatformTransactionManager事务继承示例如下:
Spring事务隔离级别和传播级别
TransactionDefinition接口中定义了Spring事务隔离级别和Spring事务传播级别。隔离级别主要控制事务并发访问时隔离程度。Spring支持的隔离级别如下:
除了使用ISOLATION_DEFAULT表示使用数据库默认的隔离级别外,其余四个隔离级别与数据库规范的隔离级别一致。
需要注意的是,隔离级别越高,意味着数据库事务并发执行性能越差。JDBC规范虽然定义了事务支持的以上行为,但是各个JDBC驱动、数据库厂商对事务的支持程度可能各不相同。出于性能的考虑我们一般设置READ_COMMITTED级别。针对READ_COMMITTED隔离级别无法避免的脏读,通常使用数据库的锁来处理。
传播级别主要控制含事务方法的调用(如一个事务方法调用另一个事务方法)时,Spring对事务的处理方式。Spring事务传播级别共七类。它们是:
(1)PROPAGATION_REQUIRED:支持当前事务,如果当前有事务则加入,如果当前没有事务则新建一个。这种方式是默认的事务传播方式。(一般直接使用,不需要调整)
(2)PROPAGATION_SUPPORTS:支持当前事务,如果当前有事务则加入,如果当前没有事务则以非事务方式执行。
(3)PROPAGATION_MANDATORY:支持当前事务,如果当前有事务则加入,如果当前没有事务则抛出异常。(当前必须有事务)
(4)PROPAGATION_REQUIRES_NEW:不支持当前事务,如果当前有事务则挂起当前事务,然后新创建一个事务,如果当前没有事务则自己创建一个事务。
(5)PROPAGATION_NOT_SUPPORTED:不支持当前事务,如果当前有事务则把当前事务挂起,执行完后恢复事务(忽略当前事务)。
(6)PROPAGATION_NEVER:不支持当前事务,如果当前存在事务,则抛出异常。(当前必须不能有事务)
(7)PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中。如果当前没有事务,则新建一个事务自己执行。对嵌套事务来说,内部事务回滚时不会影响外部事务的提交;但是外部事务回滚会把内部事务一起回滚。(这个和新建一个事务的区别)
Spring事务失效
常见的事务失效场景有:
(1) 异常类型不对:默认支持回滚的是 Runtime 异常,或异常被业务捕获。
(2) 数据源不支持事务:如 MySQL 未开启事务或使用 MyISAM 存储引擎。
(3) 非 Public 方法不支持事务。
(4) Spring传播类型不支持事务。
(5) 事务未被 Spring 接管。
Spring事务有优势
(1) 为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
(2) 为编程式事务管理提供了一套简单的API而不是一些复杂的事务API
(3) 支持声明式事务管理。
(4) 和Spring各种数据访问抽象层很好得集成。
参考
https://blog.csdn.net/adminpd/article/details/123016872 常见Java后端面试题系列——Spring篇
https://blog.csdn.net/qq_57434877/article/details/123714044 spring创建Bean的流程以及Bean的生命周期
https://thinkwon.blog.csdn.net/article/details/104397516 Spring面试题
https://blog.csdn.net/adminpd/article/details/123016872 Java后端面试题系列——Spring篇
https://zhuanlan.zhihu.com/p/623502268 Spring面试题详解—从基础到进阶
https://zhuanlan.zhihu.com/p/493343355 Spring面试题
https://blog.csdn.net/q982151756/article/details/80513340 细说Spring——AOP详解(AOP概览)
https://blog.51cto.com/5914679/2092253#h10 Spring AOP 切点(pointcut)表达式
https://blog.csdn.net/weixin_46009162/article/details/113333311 Spring boot AOP结合注解的使用
https://cloud.tencent.com/developer/article/1441626 spring aop概念、使用、动态代理原理
https://www.jianshu.com/p/5b9a0d77f95f spring aop 及实现方式
https://www.cnblogs.com/orzjiangxiaoyu/p/13869747.html Spring-AOP-基于注解的AOP通知执行顺序
https://blog.csdn.net/hxpjava1/article/details/55504513 spring多个AOP执行先后顺序
https://www.jianshu.com/p/f7238613c877 Spring AOP注解@DeclareParents的使用
https://www.cnblogs.com/chihirotan/p/7365890.html Spring AOP中引入增强
https://www.cnblogs.com/wangshen31/p/9383828.html 用注解@DelcareParents实现引用增强
https://blog.csdn.net/a745233700/article/details/80959716 Spring常见面试题总结