Spring
- 参考链接
- 1.spring是啥?
- 2.优点
- 3.IOC
- 4.AOP
- 6.容器的启动流程
- 7.BeanFactory和ApplicationContext的关系
- 9.Bean的作用域
- 10.Bean是线程安全的吗,如果不安全,如何处理
- 11-1.bean的注入方式
- 11-2.spring获取bean
- 12.spring如何解决循环依赖
- 13 spring的自动装配
- 14 spring事务
- 15.spring用了哪些设计模式
- 16.注解的原理
- @Value
- 17 profile切换开发、测试、生产配置文件
- http请求工具类
参考链接
https://blog.csdn.net/a745233700/article/details/80959716
1.spring是啥?
是个轻量级的Ioc和aop容器框架。简化企业web程序开发,使得开发者可以把精力放在业务上,主要有7个模块:
- Spring Context 框架式的Bean访问方式,及一些企业级功能(JNDI、定时任务等)
- Spring Core 核心库,所有模块都依赖此库,提供IOC和DI
- Spring AOP 提供切面动态增强方法
- Spring Web 对Struts2的支持,整合试图层框架,
- Spring MVC 试图层框架替代Strust2
- Spring DAO 持久层,对JDBC的抽象封装,能统一管理JDBC事务
- Spring ORM 对现有ORM的支持
2.优点
- 代码低侵入性,
- DI机制将对象实例的依赖关系交给spring管理,降低组件耦合性
- 提供了AOP,支持将一些通用任务,安全、事务、日志、数据权限进行切面管理,提高了代码维护性,提高代码复用
- spring的扩展性很好,可整合很多其他框架
3.IOC
IOC
Inversion of Control,控制反转,对象的控制权交给Spring,由Spring负责管理对象的生命周期(创建、初始化、销毁)和其他对象间的依赖关系。
交给spring管理后,不用再new,IOC由专门的容器创建管理。
DI
依赖注入,IOC动态的向某个对象提供其需要的其他对象,就是通过DI实现,及程序在运行时依赖ioc容器动态注入对象需要的外部依赖。通过反射实现,反射可以在运行时动态生成对象实例,执行方法,改变对象属性。
ioc原理
工厂+反射,bean对象如何注册到ioc容器,以及bean对象的加载、实例化、初始化。
4.AOP
面向切面,处理一些与业务无关,公告行为和逻辑,抽取封装为可重用模块,可减少重复代码,降低耦合都,提高系统维护性,权限、日志、事务,重复提交。
注意点
被切方法尽量不要用基本数据类型,不然可能爆AopInvocationException异常,方法抛异常时会返回Null。
原理
动态代理,如果该类是实现类,用Jdk动态代理,如该类没实现接口,则用cglig的动态代理。
2种模式
- 静态代理
- 1.实现方式,2.继承方方式,共同点:代理类把目标类作为成员变量,通过构造器初始化。
- AspectJ静态代理,编译时增强,在编译阶段生成AOP代理类,植入到字节码中,运行时是增强后的对象。
- Spring AOP 动态代理,不去修改字节码,而是运行时在内存中临时为方法生成一个对象,该对象包含目标对象的全部方法,并在特定的切点做增强处理,并回调目标对象的方法。
jdk动态代理只提供接口的代理,核心是InvocationHandler接口和Proxy类,使用Proxy类来动态创建目标类的代理类(最终的代理类,这个类继承自Proxy,并实现了接口的方法),当代理对象调用真实方法时,InvocationHandler通过invoke()方法反射来调用目标类的代码,动态横切逻辑和业务。
点击查看代码
/**
* proxy 最终生成的代理对象
* method 目标的具体方法名(想代理的方法)
* args 方法参数
*/
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args);
如果被代理类没有实现接口,spring会用cglib代理目标类,cglib可在运行时动态生成指定的子类对象,并重写特定方法做增强,如果类被标记为final,则无法使用cglib做动态代理。
- 动态代理和静态代理区别在于,生成代理对象的时机不同,AspectJ静态代理有更好的性能,但需要特定的编译器进行处理,而Spring AOP无需特定的编译器处理。
jdk动态代理
procy类
点击查看代码
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
获取代理对象
public class JdkHandler implements InvocationHandler {
private Object target;
public JdkHandler(Object target){
this.target = target;
}
/**
* target 目标对象
method 想增强的目标对象的方法
args 参数列表
@return 方法的返回值
*/
@Override
public Object invoke(Object target, Method method, Object[] args)throws Throwable{
System.out.print("前置通知");
// 目标对象方法的回调
Object obj = method.invoke(target, args);
System.out.print("后置通知");
}
publict Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
}
测试
点击查看代码
public static void main(String[] args){
// 公共代理工具类
JdkHandler handler = new JdkHandler(new You());
//代理对象
Marry marr = (Marry) handler.getProxy();
// 执行增强后的方法
marr.toMarry();
JdkHandler handler2 = new JdkHandler(new Owner());
RentHouse rentHouse = (RentHouse) handler2.getProxy();
// 展示房源
rentHouse.showHouse();
}
spring aop名词
- Join point :程序执行的方法
- @Aspect: 切面,加在类上,被抽取出来的公共模块,横切多个对象,可理解为Pointcut切点和Adive通知的结合,一个切面由多个切点和通知组成。可在类上使用@AspectJ注解实现。
- @Pointcut 切点,加在方法,控制对哪些方法(Join point)做增强
切点分为execution和annotation,execution可用路径表达式指定对哪些方法拦截,如:add,find。annotation方式可以指定对哪些注解修饰的代码进行拦截。 - Advice 顶级接口,定义规范,方法的具体增强代码实现,权限校验、日志记录,数据权限sql等,可精确控制到方法前后,@Around,@Before,@After,@AfterReturning,@AfterThrowing。
-
前置通知 before advice:方法执行前
-
后置通知 after advice 方法执行完后通知,不论正常返回还是异常退出
-
环绕置通知 around advice 可在方法调用前后完成自定义行为,可选择是否继续执行连接点,或直接返回它们自己的返回值,或抛出异常结束执行。
-
返回后通知 afterReturn advice 连接点正常完成后执行的通知(如抛异常则不会执行)
-
抛出异常通知 AfterTrowing advice 方法抛出异常时执行
-
执行顺序问题:
-
没异常情况 | 抛异常 |
---|---|
around before advice | 同 |
before advice | 同 |
target method(本身的方法) | 同 |
after advice | 同 |
around after advice | 同 |
afterReturning advice | afterThrowing advice |
java.lang.RuntimeException:异常发生 |
- Target 目标:连接点方法的对象,其实是个代理对象。此对象是个代理对象。
- Weaving 织入:在目标对象target的方法(连接点Join point)中执行增强逻辑(Advic)的过程。
- Introduction 引入:添加额外方法或字段到被通知的类。Spring允许引入新的接口(对应的实现)到任何被代理的对象。
spring aop 代码
环绕通知
注解方式:
点击查看代码
@Pointcut("@annotation(cn.yottacloud.pc.module.payorder.annotation.PayRecordMsg)"
+ "|| @within(cn.yottacloud.pc.module.payorder.annotation.PayRecordMsg)")
public void pointCut() {
}
@Around("pointCut()")
public Object run2(ProceedingJoinPoint joinPoint) throws Throwable {
//获取方法参数值数组
Object[] args = joinPoint.getArgs();
//得到其方法签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获取方法参数类型数组
Class[] paramTypeArray = methodSignature.getParameterTypes();
if (EntityManager.class.isAssignableFrom(paramTypeArray[paramTypeArray.length - 1])) {
//如果方法的参数列表最后一个参数是entityManager类型,则给其赋值
args[args.length - 1] = entityManager;
}
logger.info("请求参数为{}",args);
//动态修改其参数
//注意,如果调用joinPoint.proceed()方法,则修改的参数值不会生效,必须调用joinPoint.proceed(Object[] args)
Object result = joinPoint.proceed(args);
logger.info("响应结果为{}",result);
//如果这里不返回result,则目标对象实际返回值会被置为null
return result;
}
点击查看代码
@Around(value="cut()")
public Object around(ProceedingJoinPoint pjp) {
System.out.println("环绕通知-前置通知");
// 想代理方法的返回值
Object obj = null;
try{
// 显示调用对应方法
obj = pjp.proceed();
System.out.println("环绕通知-返回通知");
}catch(Throwable th){
th.printStackTrack();
System.out.println("环绕通知-异常通知");
}
System.out.println("环绕通知-最终通知");
}
spring aop 如何扫描参数注解
思路:1:切所有的方法,在具体通知中对该方法参数2维数组遍历,定位到该注解,再对该参数处理。
2:把注解加到类和参数上,切到方法上的注解后,会执行通知,下面处理和1相同,还是需要获取2维数组。
代码:
点击查看代码
@Aspect
@Configuration
public class ValidateAop {
@Pointcut("@annotation(cn.yottacloud.pc.core.annotation.DataScope)")
public void validate(){}
/**
* 切点配置,表达式, 在com.laoxi.test.controller包下,所有的类public的任意参数的方法
*/
@Pointcut("execution(public * com.laoxi.test.controller.*.*(..))")
public void validate(){}
@Before("validate()")
public void doBefore(JoinPoint joinPoint){
Object[] params = joinPoint.getArgs();
if(params.length == 0){
return;
}
//获取方法,此处可将signature强转为MethodSignature
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//参数注解,1维是参数,2维是注解
Annotation[][] annotations = method.getParameterAnnotations();
for (int i = 0; i < annotations.length; i++) {
Object param = params[i];
Annotation[] paramAnn = annotations[i];
//参数为空,直接下一个参数
if(param == null || paramAnn.length == 0){
continue;
}
for (Annotation annotation : paramAnn) {
//这里判断当前注解是否为Test.class
if(annotation.annotationType().equals(Test.class)){
//校验该参数,验证一次退出该注解
//TODO 校验参数
break;
}
}
}
}
}
注解结合aop控制数据权限
1: pojo需要继承基类,xml只能用$。
2:在注解中从静态方法获取登录用户,或者存到对象的map。
最危险的情况:xml拼接了sql,但方法没有加注解(处理逻辑有处理sql注入),导致sql注入。
所以,拼接了sql一定要加注解!!
注解
@Lazy
- 具体逻辑源码
ContextAnnotationAutowireCandidateResolver.buildLazyResolutionProxy()生成的延迟代理对象。
@EnableAspectJAutoProxy
-
spring项目,aop要想生效,必须要加此注解。
-
proxyTargetClass属性
默认为false。如果为true,代表一定用cglib的继承来生成动态代理,无论目标类是否有实现接口。 -
exposeProxy属性
总结:
要想自己通过AopContext.currentProxy(),能获取自己的代理对象,需要满足2个条件:
1:exposeProxy = true;
2: 在同一个线程调用AopContext.currentProxy() -
默认为false
AopContext.currentProxy()就拿不到自己的代理对象;
异常信息:
java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it availabl
- 如果为 true
代表:Spring会把该代理对象放到ThreadLocal里面,才可以通过AopContext.currentProxy()获取当前类的代理对象。
6.容器的启动流程
1)初始化容器,注册内置的BeanPostProcessor的BeanDefinition到容器中:
- 实例化BeanFactory[DefaultListableBeanFactory]工厂,用于生成Bean对象。
- 实例化BeanDefinitionReader注解配置读取器,用于对特定注解(@Service,@Repository)得嘞进行读取转化为BeanDefinition对象(BeanDefinition是Spring中极其重要的概念,它存储了bean对象的所有特征信息,如:是否单例,是否懒加载,factoryBeanName)
- 实例化ClassPathBeanDefinitionScanner路径扫描器,对指定包目录进行扫描查找bean对象
2)将配置类的BeanDefinition注册到容器中
3)调用refresh()方法刷新容器:
- prepareRegresh(): 刷新前的预处理
- obtainFreshBeanFactory(): 获取在容器初始化时创建的BeanFactory。
- prepareBeanFactory(beanFactory): BeanFactory的预处理工作,向容器中添加一些组件
- postProcessBeanFactory(beanFactory): 子类重写该方法,可以实现在BeanFactory创建并预处理完成后做进一步的设置。
- invokeBeanFactoryPostProcessors(beanFactory): 在BeanFactory标准初始化之后执行BeanFactoryPostProcessor的方法,即BeanFactory的后置处理器
- registerBeanPostProcessors(beanFactory): 向容器注册Bean的后置处理器BeanPostProcessor,它的作用是干预Spring初始化Bean的流程,从而完成代理、自动注入、循环依赖等
- initMessageSource(): 初始化MessageSource组件,包含国家化 、消息绑定和解析
- initApplicationEventMulticaster(): 初始化事件派发器,在注册监听器时用到
- onRefresh(): 留给子容器、子类重写的,在容器刷新时自定义逻辑
- registerListeners(): 注册监听器,将容器的所有applicationListener注册到事件派发器中,并派发之前步奏产生的事件。
- finishBeanFactoryInitialiaztion(beanFactory): 初始化所有剩下的单实例bean,核心方法是preInstantiateSingletons(),会调用getBean()方法创建对象。
- finishRegresh(): 发布BeanFactory容器刷新完成事件。
7.BeanFactory和ApplicationContext的关系
- bf是顶级父接口,ioc的核心,定义了ioc的基本功能,包含各种bean的定义、加载、实例化、依赖注入和生命周期管理。ac接口作为BeanFactory的子接口,继承了bf的所有功能外,还提供了更多的功能:
- 继承MessageSource,支持国际化
- 资源文件访问,如URL和文件ResourceLoader
- 载入多个(有继承关系)上下文,及同时加载多个配置文件,使得每一个上下文专注于一个层
- 提供在监听器中注册Bean的事件
- 1)bf采用懒汉式注入Bean,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样就不能发现一些存在的Spring配置问题,如果Bean的某一属性没有注入,BeanFactory加载后,直至第一次调用getBean方法才会抛出异常。
2) ac是饿汉式在容器启动时一次性创建了所有Bean,这样在容器启动时,可以发现配置错误,有利于检查依赖注入属性注入是否成功。
3)ac启动后预加载所有单例bean,运行时速度较快,因为容器启动时已经创建,相较bf,ac更占用内存,当配置bean较多时,启动更慢。 - bf和ac都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者的区别是:bf需手动注册,而ac是自动注册。
- bf通常以编程方式创建,ac还能以声明的方式创建,如ContextLoader.
8 Bean的生命周期
四个阶段:实例化Instantiation、属性赋值 Populate、初始化Initialization、销毁Destruction
- 实例化Bean
BeanFactory容器,当客户端向容器请求一个尚未初始化的Bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
ac容器,当容器启动结束后,通过获取BeanDefinition对象的信息,实例化所有bean. - 设置对象属性(DI)
实例化后的对象被封装在BeanWrapper对象中,然后Spring根据BeanDefinition中的信息及通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。 - 处理Aware接口
Spring会检测该对象是否实现了xxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:
- 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanid),传入Bean的名字。
- 如这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
- 如Bean实现了BeanFactoryAware接口,会调用它重写的setBeanFactory(),传参是Spring工厂自身。
- 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationnContext),传入Spring上下文。
- BeanPostProcessor前置处理: 系统想做些自定义的前置处理,可以让Bean实现BeanPostProcessor接口,重写postProcessBeforeInitialization(Object ojb, String str).
- InitializingBean:Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
- init-method: bean在Spring配置文件中配置了init-method属性,则会自动调用配置的初始化方法。
- BeanPostProcessor后置处理: Bean实现了BeanPostProcessor接口,会调用postProcessAfterInitialization(Object obj,String str),这个方法是在Bean初始化结束时调用,可以被应用于内存缓存技术。
经过以上步奏后,就可以使用bean对象了
- DisposableBean: 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用重写的destroy()方法。
- destroy-method:如果Bean配置了destroy-mothod属性,会自动调用其配置的销毁方法。
9.Bean的作用域
- singleton: 默认,单例
- prototype: 原型,多例,为每个bean请求创建一个实例。
- request: 为每个请求创建一个实例,在请求完成后,bean会失效gc回收。
- session: 会话期间为同一个,不同会话间隔离。
- global-session: 全局作用域,所有会话共享。
10.Bean是线程安全的吗,如果不安全,如何处理
spring的Bean线程不安全,
1) 对于prototype作用域的Bean,每次请求会新建对象,线程间不对同一类变量进行修改,不会有线程安全问题。
2) 对singleton的Bean,所有线程共享同一对象,会存在线程安全问题,如不对共享变量进行修改操作,则还是线程安全的。
解决办法:
ThreadLocal解决线程安全问题,是“空间换时间”,为每个线程提供个独立的变量副本,不同线程操作自己的线程副本变量。
线程同步synchronize枷锁是“时间换空间”,多个线程操作同一份共享变量。
11-1.bean的注入方式
xml
- set()方法
- 构造器:用index设置参数的位置,type设置参数类型
- 静态工厂注入
- 实例工厂
注解方式
11-2.spring获取bean
- 1:byName,用bean的name
- 2:byClass,用bean的反射
12.spring如何解决循环依赖
有三种情况:
- 构造方法进行依赖注入时产生的循环依赖
- setter方法依赖注入,且是多例下产生的
- setter方法依赖注入,且是单例模式下产生的
只有最后种被解决了,其他两种发生循环依赖会产生异常:
原因:
- 第一种构造方法注入,在new对象时就会阻塞,
- 第二种set方式(多例),每次getBean(),会new一个新的Bean,如此反复会有无穷无尽的Bean,最终导致oom
单例set方法引起的循环依赖,通过2级缓存和3级缓存解决。
// 一级缓存,放完全初始化好的bean,可以直接用的
private final Map<String,Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存,提前曝光的单例对象的cache,存放原始的bean对象(尚未填充属性的)
private final Map<String,Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存,单例对象工厂的cache,存放bean工厂对象,value是ObjectFactory函数式接口,目的是保证在整个容器运行过程中同名的bean对象只有一个。
private final Map<String,Object> singletonFactories = new HashMap<>(16);
13 spring的自动装配
spring用autowire配置自动装配模式,对象无需自己创建,由容器负责把需要的相互协作的对象引用赋予各个对象。
- xml配置有5种自动装配:
- no: 不进行自动装配,通过手工设置ref属性进行装配bean.
- byName: 通过bean的名称自动装配,如果一个bean的property和另一个bean的name相同,就进行自动装配。
- byType: 通过参数的数据类型进行自动装配。
- constructor: 利用构造函数进行装配,构造函数的参数通过byType进行装配。
- autodetect: 自动探测,如果有构造器,用construct方式自动装配,否则用byType方式自动装配。
- 基于注解
@Autowired,@Resource指定bean,使用@Autowired需要在spring配置文件进行配置,启动ioc时,容器自动装载了个AutowireAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowired,@Resource或@Inject,会在容器查找bean,并装配给该对象属性,使用@Autowired,先在对应类型的bean:
如果上面查找都找不到,抛出异常,因为@Autowired默认为必须,可改为required=false;
@Autowired可用于构造器、成员属性、set方法上。
@Autowired,@Resource的区别:
如果注解下的类只有一个子类(实现类),并且未指定名称和类型,都是按该类或子类(实现类)名在容器中查找。
@Autowired如果有多个子类(实现类),那@Autowired如果还是默认必须,可改为required=false,spring需成员变量名和某一个类名保持一致(变量名首字母小写),如变量名和类名不一致,则需要加@Qualifier("User3Dao")指定具体的对象,"User3Dao"需和实现类指定的名称相同;
@Resource,1:如果不指定name和type,则按变量名首字母大写后的类名查找。2;指定了name或type,则按它去查找装配,3:同时指定了name和type,则两个属性的值都要做校验,任意一个错了都不行。
14 spring事务
本质还是需要数据库的支持,spring是提供统一事务管理接口,具体由各数据库自己实现,spring在事务开始时根据当次连接的隔离级别,调整隔离级别。
事务的种类
编程式、声明式事务;
- 编程式事务使用TransactionTemplate。
- 声明式事务本质是aop实现,对方法前后进行拦截,将事务处理切面到拦截的方法中,在目标方法开始前手动开启事务,执行完方法提交或回滚。
声明式事务最大的优点是不需要在业务代码中额外添加事务管理,只需在配置文件做相关事务规则声明或添加@Transactional便可对业务逻辑事务控制,减少直接修改原代码,
2者的区别: 颗粒度不同,声明式作用于整个方法,编程式可在方法里进行更小的颗粒度控制。
@Transactional
可添加范围
方法、类、接口,优先级:就近原则,方法>类>接口;:,加在类上,类的所有方法有效,方法:本方法有效。
注解位置 | 影响范围 | 优先级 |
---|---|---|
加在接口 | 所有接口方法有效 | 最低 |
加在类上 | 类的所有方法有效 | 中 |
方法 | 本方法有效 | 最高 |
传播机制
定义:2个加了注解@Transactional的方法相互调用,spring如何处理这些事务,使用TreadLocal实现,如调用的方法在新线程调用,事务传播会失效。
1)PROPAGATION_REQUIRED :默认,必须有事务,别的方法掉我,有就加入它,没有,我自己新建一个事务,2个事务方法都是此属性时,内部事务爆异常,外部事务方法try了,依然外部sql会回滚,因为spring捕获异常后,事务将会被设置全局rollback,这时由于事务状态为rollback,spring认为不应该commit提交该事务,就会回滚该事务;而非事务方法调事务方法时,事务方法报错,非事务方法try了后,原来的非事务方法的每一条sql是自动提交事务
2)PROPAGATION_REQUIRES_NEW:必须以新事务执行,不论是否有事务,都创建新事务,原来有,将原来的挂起,无论是否为本类,2个方法涉及修改同一行,会引起死锁。
- 应用场景:
不受其他方法影响,我本方法独立保证提交和回滚。调我的事务方法回滚了,我照样单独提交成功,不受外层事务影响。(除非发生死锁)
3)PROPAGATION_SUPPORTS:佛系,如调我的方法已存在事务,则加入,没有我也以非事务执行。
4)PROPAGATION_NOT_SUPPORTED:以非事务方式执行(事务注解属性为此的方法,里面的每条sql自动提交事务)无论是否为本类,2个方法涉及修改同一行,会引起死锁,如果调我的方法有事务,则把当前事务挂起(spring会把当前事务的信息保存起来,等此方法执行完后再按原来的事务传播),重新开启一个新的链接,已非事务方式执行。
5)PROPAGATION_NESTED:调我的方法已存在事务,则我以嵌套事务执行,如没有,则按默认属性执行(新建事务)。
6)PROPAGATION_MANDATORY: 调我的方法必须要有事务,如果当前有事务,则加入事务,如果没有则抛异常。
7)PROPAGATION_NEVER:必须由非事务方法掉我(事务上下文中不能有事务),如调我的方法存在事务,则抛出异常。
就是不要事务 | 可有可无 | 必须要有事务 |
---|---|---|
PROPAGATION_NEVER | PROPAGATION_SUPPORTS | PROPAGATION_REQUIRED |
PROPAGATION_NOT_SUPPORTED | PROPAGATION_REQUIRES_NEW | |
PROPAGATION_NESTED | ||
PROPAGATION_MANDATORY |
经典应用
如何内层事务报错,外层事务不受影响,不回滚?
外层用默认,内层用:nested、new,外层try掉里层的异常;里外层都用默认属性,会报UnexpectedRollbackException:Transaction rolled back because it has been marked as rollback-only
如何让外层事务爆异常了,内层事务不受影响,内层事务独立成功?
内层用requires_new
2个方法注解都是默认传播属性,里面方法报错,外面方法try-catch了,外面事务方法依然一起回滚?
自己验证
事务挂起、嵌套、暂停、保存点
0. 事务挂起
传播属性:PROPAGATION_NOT_SUPPORTED,以非事务方式执行(事务注解属性为此的方法,里面的每条sql自动提交事务),如果调我的方法有事务,则把当前事务挂起(spring会把当前事务的name,readOnly,isolationLevel,wasActive等信息保存起来,等此方法执行完后再按原来的事务传播)。
挂起事务和此属性方法,如操作同一张表,会产生死锁。
1. 嵌套事务
可理解为文件目录,一个事务里面还有一个事务,以此类推,一层包一层。执行了多次开启手动开启事务命令
情况 | sql命令效果 | 注解@Transactional的效果 |
---|---|---|
子事务回滚,父事务提交 | 父事务提交成功 | 父事务也会回滚(java方法不try异常,会一直向上抛,变成父事务方法有异常了),如想外层事务不受嵌套事务回滚影响,需try掉方法里传播的事务方法; |
父事务回滚,子事务提交 | 都回滚 | 都回滚(如想子事务不受外层事务影响,子事务需改属性:PROPAGATION_REQUIRES_NEW) |
事务注解哪些情况会失效
0:数据库本身引擎不支持事务。
1:未开启spring注解事务的支持。
2:自调用,事务方法用this.调用自己的另一个方法,this不是aop代理对象。
3:异常不是RuntimeException、error,又没有rollbackFor指定。
4:添加事务注解的类没交给spring管理。
5:方法的访问作用域需要为public
6: try-catch了异常(本方法)
spring提供的编程式事务
- 配置类注入事务管理模板
@Bean
public TransactionTemplate createTransactionTemplate(PlatformTransactionManager tm){
return new TransactionTemplate(tm);
}
- 业务中局部控制事务
点击查看代码
@Service
public class UserService {
@Autowired
private TransactionTemplate tx;
public void transfer(String source, String target,Int money){
tx.execute(new TransactionCallback<Object>(){
@Override
public Object doInTransaction(TransactionStatus status){
// 写需要进行事务控制的业务逻辑
return null;
}
});
}
}
spring事务隔离级别
- ISOLATION_DEFAULT:PlaltfromTransactionManager默认隔离级别,使用数据库的默认隔离级别。
- ISOLATION_READ_UNCOMMITTED:读未提交(脏读),一个事务可读取另一个事务修改后但未提交的数据。
- ISOLATION_READ_COMMITTED:读已提交(不可重复读),一个事务2此读同一条数据,第一次读到后,C事务修改这条数据,再次读时会读到C事务提交后的数据。
- ISOLATION_REPEATABLE_READ:可重复读(幻读),一个事务开启后,查任意次的结果都是相同的。
- ISOLATION_SERIALIZABLE:串行化,所有事务排队挨个执行。
1:事务方法里try-catch了,却报异常UnexpectedRollbackException,后面的方法执行了前端却接受的500,如何压制UnexpectedRollbackException,try外面的方法正常执行?
一般是在2个事务方法的传播属性都是默认时发生(外部方法try了里面事务方法的异常(spring更新了全局回滚标记)后,没有把异常抛出,外层事务方法正常执行完后,spring检查全局回滚标记为要回滚,所以抛出此异常如何压制UnexpectedRollbackException:Transaction rolled back because it has been marked as rollback-only),可使用spring手动事务回滚,可使方法不报错。
点击查看代码
try {
salf.userDemo();
}catch (Exception e){
// 手动设置事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
2:基于上面的情况,如何做到外层事务方法try里面方法后,不受里面方法影响?
1:里面方法的事务注解属性改为nested或NEW
15.spring用了哪些设计模式
- 工厂模式,单例容器:BeanFactory,ApplicationContext
- 单例模式,spring管理的bean默认为单例
- 策略模式,Resource的实现类,针对不同的资源文件,实现不同方式的资源获取策略
- 代理模式,aop用到了jdk动态代理,cglig动态代理
- 模板方法,将相同的代码放在父类,不同的部分交给子类重写,解决代码重复问题,JpaTemplate
- 适配器模式,AOP的增强通知,用了适配器模式,mvc也用了适配器模式
- 观察者模式,事件驱动模型就是经典应用
8)桥接模式,根据需求切换到不同的数据源,项目要连接多个数据库,每次访问根据需要切换不同的 数据库
16.注解的原理
继承Annotation的特殊接口,具体实现类时jdk动态代理生成的代理类,通过反射获取注解时,返回的也是java运行时动态生成的$Proxy代理对象,代理对象调目标对象的方法,最终调用AnnotationInvocationHandler 的invoke方法,方法会从memberValues这Map查出对应的值,而memberValues的来源是Java常量池。
元注解
名称 | 属性值 | 说明 |
---|---|---|
@Target | TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、PACKAGE | 该注解可标记的位置 |
@Retention | SOURCE(编译阶段就丢弃) ,CLASS(类加载时丢弃) ,RUNTIME(始终不会丢弃) | 注解的有效作用域 |
@Document | 注解是否会包含在 javadoc 中 | |
@Inherited | 定义该注解与子类的关系,子类是否能使用。 |
@Value
@Value | @ConfigurationProperties | |
---|---|---|
自动跳转 | 不能 | 可以(ctrl 鼠标左键) |
null检查 | 启动时默认会做null检查,注解内的值如找不到,启动报错,无法启动 | 启动不会报错,则不会做null检查 |
#、$的区别
数字用法
17 profile切换开发、测试、生产配置文件
application.yml放公用的,开发和测试命名固定格式-dev
http请求工具类
发送post请求(body为泛型对象)
点击查看代码
import com.alibaba.fastjson.TypeReference;
/**
* 新的httpPost方法,返回值是泛型
* @param url
* @param payload
* @param header
* @param typeRef
* @return
* @param <T>
* @param <R> 返回值泛型
*/
private <T, R> DingfuResult<R> httpPost2(String url, T payload, Map<String,
String> header, TypeReference<DingfuResult<R>> typeRef) {
// 设置请求头(可选)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
if (header != null) {
for (Map.Entry<String, String> entry : header.entrySet()) {
headers.add(entry.getKey(), entry.getValue());
}
}
// 创建HttpEntity
HttpEntity<T> entity = new HttpEntity<>(payload, headers);
// 使用RestTemplate发起POST请求
String response = restTemplate.postForObject(url, entity, String.class);
return JSON.parseObject(response, typeRef);
}
- 使用示例(泛型为对象):
点击查看代码
DingfuResult<ClientDetailsRedManDTO> resp = httpPost2(domain + clientDetailsUri, body, initTokenHead(),
new TypeReference<DingfuResult<ClientDetailsRedManDTO>>() {});
- 使用示例(泛型为集合):
点击查看代码
DingfuResult<List<DingfuPageDTO>> resp = httpPost2(domain + clientPageUri, req, initTokenHead(),
new TypeReference<DingfuResult<List<DingfuPageDTO>>>() {});
发送post请求(body为Map)
点击查看代码
/**
* 发送post请求 (请求头不传值)
* @param url
* @param payload
* @return
*/
private String postData(String url, Map<String, String> payload) {
// 设置请求头(可选)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 创建HttpEntity
HttpEntity<Map<String, String>> entity = new HttpEntity<>(payload, headers);
// 使用RestTemplate发起POST请求
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class
);
// 返回响应体
return response.getBody();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构