Spring拾遗
一、Spring模块划分
Spring六大模块:Core、Testing、DataAccess、web、Integration、Languages。
Core:最核心的模块,包含Bean、Context、AOP,Bean是SpringBean,为了管理Bean又引入了Context和AOP
Testing:为了方便测试,引入了Testing模块,包含Mock、TestContext
DataAccess: 为了方便做数据操作,引入了数据模块,包含事务Tx、数据库连接JDBC、ORM
web:对外提供web访问,包含Spring MVC和WebFlux
Integration: 集成各种三方组件,例如remoting、JMS、WS;
Languages: 集成其他基于JVM的语言,例如Kotlin、Groovy
如上图所示:在Spring中最核心的是Bean和AOP,为了更好的使用Bean,需要Context来管理Bean,为了更好的对外提供服务,需要提供对外访问的功能(web),也无需要访问数据库,则需要了JDBC,数据库有事务处理,则需要TX,针对需要提供的功能,首先需要有web功能,spring替换拱了Spring MVC和WebFlux,为了方便做单元测试,引入了Testing,为了更好的做好业务,又引入了Springcore、SpringBatch、Spring Security,为了更好的和三方对接,引入了Interation,为了管理其他的数据源,例如redis、mongo等,引入了统一的Data,为了跟ORM框架集成,则引入了ORM模块。
二、AOP与动态代理
1、动态代理
在Spring中有jdk的动态代理和cglib动态代理两种:
jdk基于接口,生成接口的实现类,cglib基于字节码,生成类的子类,性能相差不大,默认jdk,因为使用jdk不需要依赖三方包,但是由于必须要有接口的限制,还必须引入cglib
jdk:实现InvocationHandler接口,重新invoke方法,使用Proxy类的newProxyInstance方法,传入目标类的类加载器、目标类的接口进行调用
cjlib:实现MethodInterceptor,通过methodProxy.invokeSuper进行实现ioc:工厂+反射
这里需要说明一点,spring framework,有接口默认使用jdk,没有接口则使用cglib,如果是接口,可以使用 @EnableAspectJAutoProxy(proxyTargetClass = true) 修改代理类的方式,默认为false,即JDK自带的动态代理,如果为true,则使用cglib。但是如果使用的springboot2.x,无论是否有接口,默认都是使用cglib,这是因为在AopAutoConfiguration中设置了proxy-target-class的默认值为true,可以通过修改配置文件的方式将其改为false。
Cglib是字节码增强的技术,类似CGLIB这种字节码增强的技术还有ASM、AspectJ、JavaProxy、Javassist、Instrumentation,其中Intrumentation利用JavaAgent技术,在加载jar包或者类时对其进行增强,目前一些常见的APM工具,许多都是使用JavaAgent实现的。
目前字节码增强新工具最常用的是ByteBuddy,因为其提供了更友好的操作API。
2、AOP示例
@Aspect @Component public class LogAopService { /** * 环绕通知 */ @Pointcut(value = "execution(* com.lcl.spring.service.*.*(..))") public void point(){ } @Before(value = "point()") public void before(){ System.out.println("========== BEFORE ========"); } @AfterReturning(value = "point()") public void after(){ System.out.println("======== AFTER ========"); } @Around(value = "point()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("======== around before ========"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("======== around AFTER ========"); return proceed; } }
3、在springboot中获取SpringApplicationContext上下文的五种方式:
首先创建一个SpringBeanUtil用来存储SpringApplicationContext
public class SpringBeanUtils { private static ApplicationContext applicationContext; public static void setApplicationContext(ApplicationContext applicationContext){ SpringBeanUtils.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } }
(1)实现ApplicationContextInitializer接口
@Component public class MyApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { SpringBeanUtils.setApplicationContext(applicationContext); } }
(2)实现ApplicationListener接口
@Component public class MyApplicationListener implements ApplicationListener<ApplicationContextEvent> { @Override public void onApplicationEvent(ApplicationContextEvent event) { SpringBeanUtils.setApplicationContext(event.getApplicationContext()); } }
(3)放在启动类main方法中设置
public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); SpringBeanUtils.setApplicationContext(applicationContext); }
(4)实现ApplicationContextAware接口
@Component public class SpringBeanUtils2 implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext){ SpringBeanUtils2.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } }
最后使用SpringApplicationContext
DemoService demoService = applicationContext.getBean(DemoService.class);
三、Spring Bean的生命周期
在Spring刚开始的时候,主要的核心是Bean的管理,那么就有了BeanFactory来创建Bean对象,随着功能的增加,主键引入了环境感知的能力(EnvironmentCapable)、Bean列表能力(ListableBeanFactory)、层次结构能力(HierarchicalBeanFactory)、消息处理、事件处理能力(MessageSource)、事件发布能力(ApplicationEventpublisher)、资源模式转换能力(ResourcePatternResolver);然后Spring就将这些能力统一放到ApplicationContext容器中去,就形成了Spring最核心的容器,所有的Bean都在该容器中,Bean的生命周期也由其管理。
Spring Bean的加载都是用Beanfactory进行创建的,创建完毕后,放入ApplicationContext管理。
Spring Bean的生命周期总体可以分为:实例化、赋值、初始化、使用、销毁这几个阶段
实例化:在源码中走的是Instantiation方法
属性赋值:对于类中的属性赋值,可能需要注入其他Bean
初始化:对Bean做各种处理,处理完成后,Bean创建完毕,如果是单例的(指定作用范围为 scope=“singleton”),放入 Spring IoC 缓存池中,触发 Spring 对该 Bean 生命周期管理;如果是scope=“prototype”,交给调用者管管理
销毁:如 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如destory-method 属性指定销毁方法,调用该方法。
源码如下图所示:
初始化主要分为:
1、检查Aware相关接口并设置相关依赖
(1)如Bean 实现BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
(2)如 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
(3)如 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
2、前置过滤
如 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工,此处非常重要,Spring 的 AOP 就是利用它实现的。
3、初始化设置
(1)如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
(2)如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
4、后置过滤器
如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 可用了。
初始化源码如下图所示:
Bean的整个生命周期如下图所示:
代码演示一下:
首先是实现几个Aware接口
@AllArgsConstructor @NoArgsConstructor @ToString @Component(value = "student001") @Data public class Student implements Serializable, BeanNameAware, ApplicationContextAware { private int id; private String name; private String beanName; private ApplicationContext applicationContext; public void init(){ System.out.println("hello..........."); } public static Student create(){ return new Student(102,"KK102",null, null); } public void print() { System.out.println(this.beanName); System.out.println(" context.getBeanDefinitionNames() ===>> " + String.join(",", applicationContext.getBeanDefinitionNames())); } }
然后是实现前置过滤器和后置过滤器
@Component public class HelloBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(" ====> postProcessBeforeInitialization " + beanName +":"+ bean); // 可以加点额外处理 // 例如 if (bean instanceof Student) { Student student = (Student) bean; student.setName(student.getName() + "-" + System.currentTimeMillis()); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(" ====> postProcessAfterInitialization " + beanName +":"+ bean); return bean; } }
四、Spring 循环依赖
Spring循环依赖主要依靠提前曝光和三级缓存,其中三级缓存:
singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。
earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。
singletonFactories: 三级缓存,存储 singletonFactory。
任何对象初始化时,都是从一二三级缓存中先查找对象是否存在,如果存在,则从缓存中获取,如果不存在,再进行Bean的创建,创建完成,会放入不同的缓存中。
1、不引入其他Bean时Bean的创建过程
@Service public class CircularServiceA { private String fieldA = "字段 A"; }
Spring 在创建 Bean 的过程中重点是在 AbstractAutowireCapableBeanFactory 中的以下三个步骤:
实例化 createBeanInstance: 其中实例化 Bean 并对 Bean 进行赋值,像例子中的 fieldA 字段在这里就会赋值。
属性注入 populateBean: 可以理解为对 Bean 里面的属性进行赋值。(会依赖其他 Bean)
初始化 initializeBean: 执行初始化和 Bean 的后置处理器。
2、引入其他Bean时Bean的创建过程
@Service public class CircularServiceA { private String fieldA = "字段 A"; @Autowired private CircularServiceB circularServiceB; } @Service public class CircularServiceB { }
A对象初始化:首先调用createBeanInstance做实例化,先调用addSingletonFactory将当前Bean放入三级缓存,在调用populateBean做属性注入时,属性为B对象的Bean,那么就需要继续对B对象做初始化;
B对象初始化:首先还是调用createBeanInstance做B对象的实例化,然后将其放入三级缓存,然后做属性注入,属性注入完毕后进行初始化,初始化完成后,从二级、三级缓存中移除对象,将B添加到一级缓存;
A对象依赖的Bean初始化完成后:此时A对象属性赋值完成,继续进行初始化,初始化完成后,将A从二级三级缓存中移除,放入一级缓存。
3、存在村换依赖时Bean的创建过程
@Service public class CircularServiceA { private String fieldA = "字段 A"; @Autowired private CircularServiceB circularServiceB; } @Service public class CircularServiceB { @Autowired private CircularServiceA circularServiceA; }
循环依赖和上面的区别就在于B又引入了A,那么在流程如下:
A对象实例化:和上面一样
B对象实例化:这里就开始有区别,首先B对象依赖A对象,那么在调用populateBean方法做属性赋值的时候,需要获取对象A的实例;
获取A对象实例:那么就会从缓存中获取,此时A对象在三级缓存中,获取到后,将A对象从那个三级缓存移到二级缓存,由于二级缓存的对象已经实例化没有初始化,因此可以认为A对象可以引用。
B对象初始化:B对象此时已经拿到A对象的实例,则B对象可以进行初始化,初始化完成后将B添加到一级缓存,并从二级三级缓存中删除。
A对象初始化:A对象此时也拿到了B对象的实例,因此也可以做初始化,初始化完成后,将A添加到一级缓存,并从二级三级缓存中删除。
4、为什么要使用三级缓存解决循环依赖
使用二级缓存是可以的,使用三级缓存主要的考虑点是因为动态代理。
在Spring中,初始化Bean的方法initializeBean方法,这里需要初始化Bean并处理后置处理器,其中有一个处理器为:AnnotationAwareAspectJAutoProxyCreator其实就是加的注解切面,会跳转到AbstractAutoProxyCreator 类的 postProcessAfterInitialization 方法,这里就是Spring AOP的处理,最终返回的是一个代理对象,
如果是有循环依赖的动态代理:
创建 B 的时候,需要从三级缓存获取 A。此时在 getSingleton 方法中会调用:singletonObject = singletonFactory.getObject(); 这一块调用的是 getEarlyBeanReference,开始遍历执行 BeanPostProcessor。也就是说此时返回,并放到二级缓存的是一个 A 的代理对象。这样 B 就创建完毕了!到 A 开始初始化并执行后置处理器了!因为 A 也有代理,所以 A 也会执行到 postProcessAfterInitialization 这一部分!
可以看到,循环依赖下,有没有代理情况下的区别就在:singletonObject = singletonFactory.getObject();。在循环依赖发生的情况下 B 中的 A 赋值时:无代理:getObject 直接返回原来的 Bean;有代理:getObject 返回的是代理对象;然后都放到二级缓存。
5、使用二级缓存是否可以?或者说singletonFactories有什么作用?去掉singletonFactories行不行
去掉三级缓存之后,Bean 直接创建 earlySingletonObjects, 看着好像也可以。如果有代理的时候,在 earlySingletonObjects 直接放代理对象就行了。但是会导致一个问题:在实例化阶段就得执行后置处理器,判断有 AnnotationAwareAspectJAutoProxyCreator 并创建代理对象。
这么一想,是不是会对 Bean 的生命周期有影响。
同样,先创建 singletonFactory 的好处就是:在真正需要实例化的时候,再使用 singletonFactory.getObject() 获取 Bean 或者 Bean 的代理。相当于是延迟实例化。
6、使用二级缓存是否可以?去掉二级缓存行不行
如果去掉了二级缓存,则需要直接在 singletonFactory.getObject() 阶段初始化完毕,并放到一级缓存中。
那有这么一种场景,B 和 C 都依赖了 A。要知道在有代理的情况下 singletonFactory.getObject() 获取的是代理对象。多次获取代理对象不同,而多次调用 singletonFactory.getObject() 返回的代理对象是不同的,就会导致 B 和 C 依赖了不同的 A。
7、注意点
Spring只能解决基于属性的循环依赖,如果是基于构造函数的循环依赖,则解决不了,因为实例化是就会循环等待,相当于造成死锁。
五、Spring XML原理
1、xsd与配置文件
描述XML文件格式定义有两种方式:XSD(XML Schema定义)、DTD(文档类型定义),Spring中用的是XSD,在Spring中,所有的XSD都被汇总到spring.schemas文件中,在开发时期,使用其定义和限制XML配置文件中的标签名称,而在Spring运行时,是需要将XML中定义的Bean加载成文真正的对象Bean,这块由spring.handler来控制。
如下是Spring的XML配置文件,其中xmlns表示xml的namespace,如果不加冒号,表示当前的空间,如果加了冒号,表示指定的空间。在使用时,标签的名称需要加上xml的namespace,如果是默认的,不需要加。
<?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-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.lcl.spring02" /> <bean id="aop1" class="com.lcl.spring02.Aop1" />
和xmlns对应的是xsi,其表示不同的xmlns对应的描述文件所在的地址,该地址都是网络地址。如果电脑不能联网,实际上在jar包的spring.schemas文件中也配置了全量的描述文件地址,在spring.schemas中配置的有具体对应的地址
综上,在使用XML配置Spring时,其原理是:
定义xsd文件,定义各种自定义标签
在spring的配置文件中,通过schema.location找到对应的xsd文件,然后使用xsd用来检查配置的标签是否正确,如果不能联网,还提供了spring.schemas来获取到xsd
是spring容器初始化过程中,通过spring.handlers用来从xml文件中加载解析成dom树,并加载成为Bean。
例如像Dubbo等项目,都是对其中的XML做了重写。
2、 简化编写xsd
XML的xsd文件编写起来非常的繁琐,那如何能简化xsd文件编写呢?在Spring-commons中有一个XmlBeans,其可以来做Bean和xsd之间的转换,因此就产生了一个Spring-xbean的插件,其可以根据Bean字段的定义自动生成XSD文件,并根据Bean的字段结构,加载XML文件。好处是我们不需要再自己定义繁琐的XSD文件,缺点是丧失了灵活性,XML的标签属性必须要和Bean的属性一致。
解析XML文件的方式有DOM和SAX两种,其中DOM是将XML全部加载到内存中,然后将文件解析成为DOM树,常见的技术有dom4j,SAX是流式的加载XML。
SAX可以快速扫描一个大型的XML文档,当它找到查询标准时就会立即停止,然后再处理之。DOM是把XML全部加载到内存中建立一棵树之后再进行处理。所以DOM不适合处理大型的XML【会产生内存的急剧膨胀】。同理,DOM的弱项就是SAX的强项,SAX不必把全部的xml都加载到内存中。但是SAX的缺点也很明显,它只能对文件顺序解析一遍,不支持对文件的随意存取。SAX也仅仅能够读取文件的内容,并不能修改内容。DOM可以随意修改文件树,从而修改了xml文件。
XML和Bean之间的相互转换,除了上面提到的Spring-xbean之外,还有xStream。
3、SpringBean配置方式的演进变化
上面描述了XML配置的方式,但是实际上现在已经很少使用XML的配置方式了,SpringBean配置方式的演变如下:
刚开始时,使用的全都是XML配置
spring 1.0-2.0版本时,增加了@Autowire注解,是XML配置+注解注入的方式
spring 2.5,增加了@Service注解,实现了半自动注解
spring 3.0,增加了@Bean和@Configration注解,实现了Java Config的配置
spring 4.0 和Springboot,增加了@Condition和@AutoConfigX,实现了全自动的注解配置,可以动态的做配置
六、其他
1、resource和autowired的区别是什么;多个实现类使用autowired会有问题么:
autowired是spring的,默认按类型装配,autowired是jdk的默认按名称装配;多个实现类使用autowired启动会报错,可以使用@Qualifier设置装配名称
2、Spring中有哪些设计模式
1、工厂模式:Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象(两者对比:BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),相比于BeanFactory 来说会占用更少的内存,程序启动速度更快。ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用ApplicationContext会更多。ApplicationContext的三个实现类:ClassPathXmlApplication:把上下文文件当成类路径资源。FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息。)
2、模版模式,在各种BeanFactory以及ApplicationContext实现中也都用到了
3、代理模式,Spring AOP 利用了 AspectJ AOP实现的! AspectJ AOP 的底层用了动态代理
4、策略模式,加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的借口Resource;在Aop的实现中,采用了两种不同的方式,JDK动态代理和CGLIB代理
5、单例模式,比如在创建bean的时候。
6、观察者模式,spring中的ApplicationEvent,ApplicationListener,ApplicationEventPublisher
7、适配器模式,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter,AfterReturningAdapter
8、装饰者模式,源码中类型带Wrapper或者Decorator的都是
3、spring有多少IOC容器:
Spring 提供了两种( 不是“个” ) IoC 容器,分别是 BeanFactory、ApplicationContext 。BeanFactory ,就像一个包含 Bean 集合的工厂类。它会在客户端要求时实例化 Bean 对象。ApplicationContext 接口扩展了 BeanFactory 接口,它在 BeanFactory 基础上提供了一些额外的功能。
区别:BeanFactory使用懒加载,ApplicationContext即时加载;Beanfactory显式提供资源对象,ApplicationContext自己创建和管理对象;Beanfactory不支持国际化,ApplicationContext支持;Beanfactory不支持基于依赖的注解,ApplicationContext支持;Beanfactory被称为低级容器,ApplicationContext被称为高级容器。
BeanFactory 最常用的是 XmlBeanFactory 。它可以根据 XML 文件中定义的内容,创建相应的 Bean。
常见的 ApplicationContext 实现方式:
1、ClassPathXmlApplicationContext :从 ClassPath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。示例代码如下:ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。示例代码如下:ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
3、XmlWebApplicationContext :由 Web 应用的XML文件读取上下文。例如我们在 Spring MVC 使用的情况。
4、目前我们更多的是使用 Spring Boot 为主,所以使用的是第四种 ApplicationContext 容器,ConfigServletWebServerApplicationContext 。
4、Spring 提供了以下五种标准的事件:
上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext 被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的 #refresh() 方法时被触发。
上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext 的 #start() 方法开始/重新开始容器时触发该事件。
上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的 #stop() 方法停止容器时触发该事件。
上下文关闭事件(ContextClosedEvent):当ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁。
请求处理事件(RequestHandledEvent):在 We b应用中,当一个HTTP 请求(request)结束触发该事件。
除了上面介绍的事件以外,还可以通过扩展 ApplicationEvent 类来开发自定义的事件:继承ApplicationEvent自定义事件类,继承ApplicationListener自定义事件监听,通过 ApplicationContext 接口的 #publishEvent(Object event) 方法,来发布自定义事件
5、如何实现一个IOC容器:
先准备一个基本的容器对象,包含一些map结构的集合,用来方便后续过程中存储具体的对象
进行配置文件的读取工作或者注解的解析工作,将需要创建的bean对象都封装成BeanDefinition对象存储在容器中
容器将封装好的BeanDefinition对象通过反射的方式进行实例化,完成对象的实例化工作
进行对象的初始化操作,也就是给类中的对应属性值就行设置,也就是进行依赖注入,完成整个对象的创建,变成一个完整的bean对象,存储在容器的某个map结构中
通过容器对象来获取对象,进行对象的获取和逻辑处理工作
提供销毁操作,当对象不用或者容器关闭的时候,将无用的对象进行销毁
6、spring支持的bean作用域有哪些?
singleton:使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。
prototype:使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。
request:该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。
session:该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。
global-session:该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。
7、Spring框架中的单例Bean是线程安全的么?
controller,service和dao本身并不是线程安全的,只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制遍历,这是自己线程的工作内存,是最安全的。
因此在进行使用的时候,不要在bean中声明任何有状态的实例变量或者类变量,如果必须如此,也推荐大家使用ThreadLocal把变量变成线程私有,如果bean的实例变量或者类变量需要在多个线程之间共享,那么就只能使用synchronized,lock,cas等这些实现线程同步的方法了。
8、事务失效场景:
选择的数据库存储引擎不支持事务
被@Transactional 注解修饰的方法为非public类型
异常没有抛出
rollbackFor默认回滚的是:RuntimeException,实际抛出的异常比这个大,例如Exception
本类方法调用,可以重新获取apo的方式:AopContext.currentProxy()).method
事务传播机制不对,例如A方法有事务,调用B方法,B方法的事务设置为不支持原事务
没有被spring管理
9、事务传播属性:
PROPAGATION_REQUIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。
PROPAGATION_SUPPORTS:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。
PROPAGATION_MANDATORY:该传播级别要求上下文中必须存在事务,否则抛出异常。
PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)
PROPAGATION_NOT_SUPPORTED:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)
PROPAGATION_NEVER:该传播级别要求上下文中不能存在事务,否则抛出异常。
PROPAGATION_NESTED:嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务。(save point概念)
10、如何自定义一个注解:
1、拦截器 + 注解 场景:
定义注解类,使用@interface修饰,设置元注解,@Target表示注解可以修饰的作用域(类、方法、属性)@Retention表示注解的生命周期(源文件阶段、编译阶段、运行阶段)@Documented表示是否生成javaDoc,@Inherited表示子类是否可以继承父类的该注解
自定义拦截器,验证拦截的方法或类上是否有自定义的注解,如果有,则作响应的处理
2、AOP + 注解 实现优雅分布式锁/缓存场景
定义注解
定义AOP切面,在切面中配置环绕通知的包,在环绕通知中进行加锁解锁操作
11、MVC 各个模块职责
DispatcherServlet:中央控制器,把请求给转发到具体的控制类
Controller:具体处理请求的控制器
HandlerMapping:映射处理器,负责映射中央处理器转发给controller时的映射策略
ModelAndView:服务层返回的数据和视图层的封装类
ViewResolver:视图解析器,解析具体的视图
Interceptors :拦截器,负责拦截我们定义的请求然后做处理工作
DispatcherServlet拦截请求,根据请求的url调用handlermapping获取对应的handler,根据handler调用对应的handleradpter,然后由handleradpter调用hander,hander执行完毕后返回给handleradpter、DispatcherServelet,然后DispatcherServelet调用事务解析器进行解析渲染。
在应用启动的时候,Spring容器会加载这些Controller类,并且解析出URL对应的处理函数,封装成Handler对象,存储到HandlerMapping对象中。当有请求到来的时候,DispatcherServlet从HanderMapping中,查找请求URL对应的Handler,然后调用执行Handler对应的函数代码,最后将执行结果返回给客户端
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!