Spring

目录

参考链接

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.优点

  1. 代码低侵入性,
  2. DI机制将对象实例的依赖关系交给spring管理,降低组件耦合性
  3. 提供了AOP,支持将一些通用任务,安全、事务、日志、数据权限进行切面管理,提高了代码维护性,提高代码复用
  4. 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的关系

  1. bf是顶级父接口,ioc的核心,定义了ioc的基本功能,包含各种bean的定义、加载、实例化、依赖注入和生命周期管理。ac接口作为BeanFactory的子接口,继承了bf的所有功能外,还提供了更多的功能:
  • 继承MessageSource,支持国际化
  • 资源文件访问,如URL和文件ResourceLoader
  • 载入多个(有继承关系)上下文,及同时加载多个配置文件,使得每一个上下文专注于一个层
  • 提供在监听器中注册Bean的事件
  1. 1)bf采用懒汉式注入Bean,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样就不能发现一些存在的Spring配置问题,如果Bean的某一属性没有注入,BeanFactory加载后,直至第一次调用getBean方法才会抛出异常。
    2) ac是饿汉式在容器启动时一次性创建了所有Bean,这样在容器启动时,可以发现配置错误,有利于检查依赖注入属性注入是否成功。
    3)ac启动后预加载所有单例bean,运行时速度较快,因为容器启动时已经创建,相较bf,ac更占用内存,当配置bean较多时,启动更慢。
  2. bf和ac都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者的区别是:bf需手动注册,而ac是自动注册。
  3. bf通常以编程方式创建,ac还能以声明的方式创建,如ContextLoader.

8 Bean的生命周期

四个阶段:实例化Instantiation、属性赋值 Populate、初始化Initialization、销毁Destruction

image

  1. 实例化Bean
    BeanFactory容器,当客户端向容器请求一个尚未初始化的Bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
    ac容器,当容器启动结束后,通过获取BeanDefinition对象的信息,实例化所有bean.
  2. 设置对象属性(DI)
    实例化后的对象被封装在BeanWrapper对象中,然后Spring根据BeanDefinition中的信息及通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。
  3. 处理Aware接口
    Spring会检测该对象是否实现了xxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:
  • 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanid),传入Bean的名字。
  • 如这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
  • 如Bean实现了BeanFactoryAware接口,会调用它重写的setBeanFactory(),传参是Spring工厂自身。
  • 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationnContext),传入Spring上下文。
  1. BeanPostProcessor前置处理: 系统想做些自定义的前置处理,可以让Bean实现BeanPostProcessor接口,重写postProcessBeforeInitialization(Object ojb, String str).
  2. InitializingBean:Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
  3. init-method: bean在Spring配置文件中配置了init-method属性,则会自动调用配置的初始化方法。
  4. BeanPostProcessor后置处理: Bean实现了BeanPostProcessor接口,会调用postProcessAfterInitialization(Object obj,String str),这个方法是在Bean初始化结束时调用,可以被应用于内存缓存技术。

经过以上步奏后,就可以使用bean对象了

  1. DisposableBean: 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用重写的destroy()方法。
  2. 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配置自动装配模式,对象无需自己创建,由容器负责把需要的相互协作的对象引用赋予各个对象。

  1. xml配置有5种自动装配:
  • no: 不进行自动装配,通过手工设置ref属性进行装配bean.
  • byName: 通过bean的名称自动装配,如果一个bean的property和另一个bean的name相同,就进行自动装配。
  • byType: 通过参数的数据类型进行自动装配。
  • constructor: 利用构造函数进行装配,构造函数的参数通过byType进行装配。
  • autodetect: 自动探测,如果有构造器,用construct方式自动装配,否则用byType方式自动装配。
  1. 基于注解
    @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在事务开始时根据当次连接的隔离级别,调整隔离级别。

事务的种类

编程式、声明式事务;

  1. 编程式事务使用TransactionTemplate。
  2. 声明式事务本质是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注解事务的支持。
image
2:自调用,事务方法用this.调用自己的另一个方法,this不是aop代理对象。
3:异常不是RuntimeException、error,又没有rollbackFor指定。
4:添加事务注解的类没交给spring管理。
5:方法的访问作用域需要为public
6: try-catch了异常(本方法)

spring提供的编程式事务

  1. 配置类注入事务管理模板

@Bean
public TransactionTemplate createTransactionTemplate(PlatformTransactionManager tm){
return new TransactionTemplate(tm);
}

  1. 业务中局部控制事务
点击查看代码
@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事务隔离级别

  1. ISOLATION_DEFAULT:PlaltfromTransactionManager默认隔离级别,使用数据库的默认隔离级别。
  2. ISOLATION_READ_UNCOMMITTED:读未提交(脏读),一个事务可读取另一个事务修改后但未提交的数据。
  3. ISOLATION_READ_COMMITTED:读已提交(不可重复读),一个事务2此读同一条数据,第一次读到后,C事务修改这条数据,再次读时会读到C事务提交后的数据。
  4. ISOLATION_REPEATABLE_READ:可重复读(幻读),一个事务开启后,查任意次的结果都是相同的。
  5. 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用了哪些设计模式

  1. 工厂模式,单例容器:BeanFactory,ApplicationContext
  2. 单例模式,spring管理的bean默认为单例
  3. 策略模式,Resource的实现类,针对不同的资源文件,实现不同方式的资源获取策略
  4. 代理模式,aop用到了jdk动态代理,cglig动态代理
  5. 模板方法,将相同的代码放在父类,不同的部分交给子类重写,解决代码重复问题,JpaTemplate
  6. 适配器模式,AOP的增强通知,用了适配器模式,mvc也用了适配器模式
  7. 观察者模式,事件驱动模型就是经典应用
    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检查

#、$的区别
数字用法
image

image

image

17 profile切换开发、测试、生产配置文件

application.yml放公用的,开发和测试命名固定格式-dev
image

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();
    }
posted @   jf666new  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示