6.【spring面试题】

spring事务传播特性(7种)。
  • 必须有事务的
    • REQUIRED如果没有,就新建一个事务,如果有,就加入当前事务(事务合并成一个)
    • REQUIRES_NEW:有没有都新建事务,如果原来有,就将原来的挂起
    • nested(嵌套): 如果没有,就新建一个事务,如果有,就在当前事务中嵌套其他事务
    • mandatory(强制性):如果没有就抛出异常,如果有,就使用当前事务
  • 可有可无的
    • supports:有就用,没有就算了
  • 死活都不要事务的
    • never:没有就非事务执行,有就抛出异常
    • not_supported:没有就非事务执行,有就挂起。然后非事务执行

验证方法如下:
首先建立两张表:sell_info和sell_info1(两张表字段一模一样,就表名不一样。)
在这里插入图片描述
然后建立一个service

	//新增数据2
	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void insert1(SellerInfo sellerInfo){
		sellerInfoRepository.save(sellerInfo);
	}

	//新增数据2
	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void insert2(SellerInfo2 sellerInfo2){
		sellerInfo2Repository.save(sellerInfo2);
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void insert3Throw(SellerInfo2 sellerInfo2){
		sellerInfo2Repository.save(sellerInfo2);
		throw new RuntimeException();
	}

场景1:REQUIRED事务,总结----外层没有事务,内层的事务独立运行,外层有事务,内层事务合并为外层。最外层为主。

//新增数据2
	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void insert1(SellerInfo sellerInfo){
		sellerInfoRepository.save(sellerInfo);
	}

	//新增数据2
	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void insert2(SellerInfo2 sellerInfo2){
		sellerInfo2Repository.save(sellerInfo2);
	}


	@Test
	@DisplayName("REQUIRED:最外层没有事务,内层两个事务,可看成独立的两个单元,互不影响")
	public void notransaction() throws Exception {
		String sellId = UUID.randomUUID().toString().replace("-","").substring(0,10);
		SellerInfo sellerInfo = new SellerInfo(sellId,"username","3456","111");
		sellerService.insert1(sellerInfo);

		SellerInfo2 sellerInfo2 = new SellerInfo2(sellId,"username2","3456","111");
		sellerService.insert2(sellerInfo2);
		throw new RuntimeException();
		//这insert都执行成功
	}

	@Test
	@DisplayName("REQUIRED:最外层没有事务,内层两个事务,可看成独立的两个单元,互不影响,有一个事务失败,不影响第二个事务执行成功")
	public void notransaction_insert3_throw() throws Exception {
		String sellId = UUID.randomUUID().toString().replace("-","").substring(0,10);
		SellerInfo sellerInfo = new SellerInfo(sellId,"username","3456","111");
		sellerService.insert1(sellerInfo);

		SellerInfo2 sellerInfo2 = new SellerInfo2(sellId,"username2","3456","111");
		sellerService.insert3Throw(sellerInfo2);
		throw new RuntimeException();
		//这里insert3回滚,insert1执行成功
	}
	
	@Test
	@DisplayName("REQUIRED:外层开启一个事务,内层的两个事务,直接统一归外层管理,只要有异常,不管是内层还是外层都执行失败")
	@Transactional(propagation = Propagation.REQUIRED)
	public void transaction() throws Exception {
		String sellId = UUID.randomUUID().toString().replace("-","").substring(0,10);
		SellerInfo sellerInfo = new SellerInfo(sellId,"username","3456","111");
		sellerService.insert1(sellerInfo);

		SellerInfo2 sellerInfo2 = new SellerInfo2(sellId,"username2","3456","111");
		// sellerService.insert2(sellerInfo2);
		sellerService.insert3Throw(sellerInfo2);
		// throw new RuntimeException();
	}

  • 场景2:REQUIRES_NEW:挂起原来的事务。
    • 外层没有事务,内层一个a是REQUIRED,b是REQUIRES_NEW,他们俩互不影响,各种执行各自的。
    • 外层有REQUIRED且外层抛异常,内层a是REQUIRED,b是REQUIRES_NEW,那么a受到最外层的影响,会回退,b单独事务,不受影响,执行成功。
    • 外层有REQUIRED,内层a普通事务,b是new,c是new抛异常,结果是b成功,a,c全部失败(原因,c失败,整个方法都失败了,导致a失败。)
@Test
@Transactional(propagation = Propagation.REQUIRED)
public void transaction() throws Exception {
  		String sellId1 = UUID.randomUUID().toString().replace("-","").substring(0,10);
		String sellId2 = UUID.randomUUID().toString().replace("-","").substring(0,10);
		String sellId3 = UUID.randomUUID().toString().replace("-","").substring(0,10);
		SellerInfo sellerInfo = new SellerInfo(sellId1,"username","3456","111");
		sellerService.a_required(sellerInfo);

		SellerInfo2 sellerInfo2 = new SellerInfo2(sellId2,"username2","3456","111");
		sellerService.b_new(sellerInfo2);
		SellerInfo2 sellerInfo3 = new SellerInfo2(sellId3,"username3","3456","111");
		sellerService.c_new_throw(sellerInfo3);
}
外层Required,c是new且抛出异常,b是new,a是普通事务,那么结果是c异常带动整个方法都抛出异常了,这个异常造成了a失败回滚,带动a也失败。b成功
  • 场景3:nested(嵌套)—如果没有则创建一个事务,如果有就在当前事务中嵌套其他事务

    • 外层没有事务,内层a和b有nested事务:外层抛出异常,也不影响a,b,a,b是独立的

    • 外层有事务,内层a,b是nested事务,c是nested抛出异常,那么abc全部失败。

      总结:外层有事务 nested==required,他们两个的行为一模一样。

    总结:

  • 外层没有事务:

    • 如果内层方法是required,nested,requires_new,每个都是独立的事务,各不影响。
  • 外层有事务:

    • 如果内层方法是required,nested,则看成一个整体,有异常,全部回滚。
    • 如果内存是requires_new,则以requires_new新建一个独立的事务,不影响其他人。

Q:spring是如何解决循环依赖的问题?

循环依赖的场景:a中依赖b,b中依赖a,这就是循环依赖,构造方法注入是不能解决循环依赖的

@Component
public class ServiceA {
   private ServiceB serviceB;

   public ServiceA(ServiceB serviceB) {
      this.serviceB = serviceB;
   }
}
@Component
public class ServiceB {
	private ServiceA serviceA;

	public ServiceB(ServiceA serviceA) {
		this.serviceA = serviceA;
	}
}
启动项目会报:BeanCurrentlyInCreationException异常。
<bean id="beanA"  class="com.example.demo.learn.bean.ServiceA" scope="singleton">
    <property name="serviceB" ref="beanB"  />
</bean>
<bean id="beanB"  class="com.example.demo.learn.bean.ServiceB"  scope="singleton">
    <property name="serviceA" ref="beanA" />
</bean>
只有set方法,且是单例的才不会爆循环依赖,如果scope="prototype" 就会抛出

Requested bean is currently in creation: Is there an unresolvable circular reference?
  • 三级缓存可以解决循环依赖。
    • singletonObjects:一级缓存(单例池:存放经历了完整生命周期的bean对象)
    • earlySingletonObjects:二级缓存(存放早期暴露出来的bean对象,bean生命周期尚未结束)
    • singletonFactories:三级缓存----单例工厂(存放可以生成bean工厂–准备生产)
DefaultSingletonBeanRegistry

一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

二级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
  • 3个map+四个方法(解决循环依赖)
    • getSingleton
    • doCreateBean
    • populateBean
    • addSingleton

在这里插入图片描述

Q:springboot自动装配原理?

前置知识:自动装配是基于条件判断来配置bean的

Configuration注解:往spring里注册组件
@Configuration(proxyBeanMethods = true)//代理组件对象
public class Myconfig {

	@Bean//注入一个属性,也是一个组件,都是单实例
	public User getUser01(){
		return new User("zhangsan","18");
	}
}
public static void main(String[] args) {
   ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
   String[] beanDefinitionNames = run.getBeanDefinitionNames();
   // for (String name : beanDefinitionNames){
   //     System.out.println(name);
   // }
   Myconfig myconfig = run.getBean("myconfig", Myconfig.class);
   System.out.println(myconfig.getUser01() == myconfig.getUser01()); 
   如果myconfig是代理对象,这里是单实例,相等的,如果是false,那么就不是单实例了
}
Import注解:导入
 * @Import({User.class, BasicConfigurator.class})
 * 给容器中自动创建这两个类的组件(无参构造器,全类名)
 必须在组件里面
 */
@Import({User.class, BasicConfigurator.class})
@Configuration
Conditional注解:条件装配注解
@Bean
@ConditionalOnBean(name = "getUser02") //有getUser02这个组件,才会注入getUser01
public User getUser01(){
   return new User("zhangsan","18");
}

// @Bean
public User getUser02(){
   return new User("lisi","20");
}

@ConditionalOnMissingBean(name = "getUser02"):没有getUser02才会注入它自身。
ImportResource:注解

导入xml的配置文件,变成组件,如果你写了xml,没有导入,启动springboot是找不到这个组件bean的

@ImportResource("classpath:springContext.xml")
ConfigurationProperties注解:配置绑定
mycar:
  name: 奥迪
  price: 100
@Component
@ConfigurationProperties(prefix = "mycar")
public class Mycar {
   private String name;
   private String price;
   //省略get set方法
}
Mycar bean = run.getBean(Mycar.class);
System.out.println("bean.getName() = " + bean.getName());   //奥迪
System.out.println("bean.getPrice() = " + bean.getPrice()); //100

EnableConfigurationProperties注解:开启自动绑定功能

@EnableConfigurationProperties(Mycar.class)
@Configuration
public class Myconfig {}

//它就不用使用@Component注解了,也能访问成功
@ConfigurationProperties(prefix = "mycar")
public class Mycar {}

主启动类有一个核心的注解@SpringBootApplication:它是一个复合的注解

SpringBootApplication包含下面三个

@SpringBootConfiguration//这里是一个Configuration注解,也就是配置注解
@EnableAutoConfiguration//这里是一个开启自动配置注解---这个注解就是自动装配注解
@ComponentScan//包扫描注解

其中最核心的就是EnableAutoConfiguration–自动装配就是由它引起的。
EnableAutoConfiguration包含这两个注解

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
  • @AutoConfigurationPackage //将启动类同级包下所有的组件注册。

    • @Import(AutoConfigurationPackages.Registrar.class)//将Registrar导入到组件中

    • Registrar就是将启动类所在的包下面的所有组件全部注册(com.example.demo),这里也就解释了,为什么启动类main必须在默认包下。

    • 这里就是将启动类同级的包下的所有组件全部注册进来
      public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
         register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
      }
      
  • @Import(AutoConfigurationImportSelector.class)将这个类导入到组件bean中。

    //获取spring-boot-autoconfigure-2.6.4.jar下meta-inf下的spring.factories所有配置+自己写的bean。(137个)

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //获取spring-boot-autoconfigure-2.6.4.jar下meta-inf下的spring.factories
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

}

在这里插入图片描述
后面就是按需加载了,比如
在这里插入图片描述

@ConditionalOnBean当容器里有指定的bean的条件下。
@ConditionalOnMissingBean当容器里不存在指定bean的条件下。
@ConditionalOnClass当类路径下有指定类的条件下
@ConditionalOnMissingClass当类路径下不存在指定类的条件下
@ConditionalOnProperty指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。

如果你想改变字符编码,有两种方式:

如果你都没写,那么springboot的组件已经定义了一个utf-8的了。这就是自动装配原理

通过配置类

@Configuration
public class Serport {

   @Bean
   public CharacterEncodingFilter characterEncodingFilter() {
      CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
      filter.setEncoding("UTF-16");
      return filter;
   }
}

2.通过配置文件

server:
  servlet:
    encoding: UTF-16

总结
总结:springboot启动的时候,会先将主配置类(启动类)所在的包以及下级的组件全部加载进来,然后会将autoconfigure.jar包里的spring.factories里的所有EnableAutoConfiguration加载进来,后面会通过条件装配注解@Conditional还有它的子注解按条件加载,如果用户在application.yml中配置了一些属性,那么就以用户的为主,如果用户在代码中配置了,那也是以用户的为主。
在这里插入图片描述

学以致用:自己手写一个。

自定义一个starter
模仿springboot

新建一个空的项目,里面包含一个helloservice的maven项目,一个autoconfuration的springboot项目。

helloservice:
    <groupId>org.example</groupId>
    <artifactId>helloservice</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>com.ifugle.configure</groupId>
            <artifactId>autoconfuration</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

autoconfuration:删掉一些。保留一些关键的

   <groupId>com.ifugle.configure</groupId>
   <artifactId>autoconfuration</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>autoconfuration</name>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter</artifactId>
      </dependency>
   </dependencies>

在autoconfuration包下新建一个service实现类(默认不放入组件中)

public class HelloService {

   @Autowired
   private HelloProperties helloProperties;

   public String sayHello(String name){
      return helloProperties.getPrefix()+":"+ name + ">>>"+helloProperties.getEndfix();
   }
}
@ConfigurationProperties(prefix = "hello.spring")
public class HelloProperties {
   private String prefix;
   private String endfix;
   //get set方法省略
}

自动装配类:HelloAutoConfiguration

@EnableConfigurationProperties(HelloProperties.class) //开启自动配置
@Configuration //是一个配置类
public class HelloAutoConfiguration {
   @Bean
   @ConditionalOnMissingBean(HelloService.class) //如果HelloService不在组件中,那么才会开启下面的组件
   public HelloService helloService(){
      HelloService helloService = new HelloService();
      return helloService;
   }
}

然后,对这两个项目打包maven clean,install,用的话是使用helloservice的包

新建一个springboot项目
在新项目中在建一个helloService组件(用来测试,如果容器中有helloService这个组件,就不会开启自己写的)

//这个类也可以不用加,只是为了测试,到底是走springboot的,还是你自己写的starter。
@Configuration
public class HelloConfig {
   @Bean
   public HelloService helloService(){
      HelloService helloService = new HelloService();
      return helloService;
   }
}
@RestController
public class TestController {

   @Autowired
   private HelloService helloService;

   @GetMapping("/hello")
   public String getHello(String name) {
      return helloService.sayHello(name);
   }
}
yml里:
hello:
  spring:
    prefix: 前缀
    endfix: 后缀
访问http://127.0.0.1:8080/hello?name=zhangsan
前缀:zhangsan>>>后缀

使用的话,直接

@RestController
public class TestController {

   @Autowired
   private HelloService helloService;

   @GetMapping("/hello")
   public String getHello(String name) {
      return helloService.sayHello(name);
   }
}
yml里:
hello:
  spring:
    prefix: 前缀
    endfix: 后缀
访问http://127.0.0.1:8080/hello?name=zhangsan
前缀:zhangsan>>>后缀
springboot启动原理
@SpringBootApplication
public class DemoApplication {
   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }
}

分两步,debug断点调试,进入run方法里。

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

主流程:上面分两步,第一步,创建(实例化),第二步运行(run)。

  • 创建

    • 关键方法:getSpringFactoriesInstances()是通过spring类加载器,加载spring.factories对应的类。

    • 从配置文件中查找BootstrapRegistryInitializer相关的启动类初始化器(空)

    • 从spring.factories查找ApplicationContextInitializer相关的应用上下文启动器,

    • 从spring.factories中查找ApplicationListener相关的应用监听器。

    • 将上面查找的所有工具类加载进来,并放入到三个它们自己对应的list集合中

    • 从配置中加载三个初始化器(bootstrap初始化器,AppContext应用上下文初始化器,Applistener应用监听器),并将这三初始化器放入到list集合中,并从所有的堆栈中查找main方法所在的堆栈,也就是主启动类。

  • 运行

    • stopWatch.start():记录应用的启动时间

    • 创建应用的引导上下文。createBootstrapContext

    • 获取所有的运行监听器SpringApplicationRunListeners

    • 配置要启动的环境。prepareEnvironment

    • 打印banner图printBanner

    • 创建应该上下文(最核心的,也就是创建ioc容器)

    • 准备应用上下文

    • 刷新应用(refresh核心方法)refreshContext

    • stop.stop(计算启动到现在的时间)并打印启动时间。

    • 所有监听器通知应用上下文listeners.started(context);

    • 如果有异常,则调用监听器的failed()方法

    • 结束,返回ioc容器。

在这里插入图片描述

Q:springbean的生命周期?

在这里插入图片描述

10.MQ队列消息
  • Q:如何保证消息不会被重复消费?
    • 生产数据时,mq内部针对每条生产者发送消息生成一个msg-id。作为去重和幂等性的依据。消费队列,必须要求消息体中有一个唯一id(主键,订单id)作为去重的依据。
    • 如果消息是数据库的insert操作,给这个消息做一个唯一的主键,有重复数据,可以使用update操作。
    • 如果是redis的话,就用set(天然去重)。
  • Q:如何处理数据丢失?
    • 启用事务(不建议,是同步了)
    • 生产者:使用confirm模式,在生产者启用confirm模式。
    • 防止数据丢失,可以开启mq的持久化。消息写入后放入磁盘。
    • 消费者:关闭mq的自动ACK
spring的过滤器和拦截器有什么区别
  • 过滤器:filter,主要实现了filter接口的三个接口,分别是初始化,dofilter,销毁。依赖于servlet容器。

    过滤拦截的是地址栏请求。过滤器实在进入容器后知行servlet之前后知行的,针对于处理业务之前的操作。

chain.doFilter(request, response)
  • 拦截器:实现的是HandlerInterceptor接口,不依赖于servlet容器,能获取到所有的类,对类里的方法进行拦截。拦截器可以注入service,也可以调用业务逻辑,是aop的一种策略,可以理解为spring的容器管理的
spring事务哪种情况下会失效
  • 数据库不支持事务,比如你的存储引擎是MyISAM。则spring事务会失效

  • 事务方法未被spring管理,会失效

    • 也就是没有被spring加载到ioc容器中。

      public class ProductService {
       @Autowired
       private ProductDao productDao;
      
       @Transactional(propagation = Propagation.REQUIRES_NEW)
       public void updateProductStockCountById(Integer stockCount, Long id){
        productDao.updateProductStockCountById(stockCount, id);
       }
      }
      
  • 方法名没有被public修饰也会失效。

     @Transactional(propagation = Propagation.REQUIRES_NEW)
     private void updateProductStockCountById(Integer stockCount, Long id){
      productDao.updateProductStockCountById(stockCount, id);
    
    
  • 同一个类有两个方法a和b,a没加,b加了,a调用b,则失效

  • 未配置事务管理器

    • @Bean
      public PlatformTransactionManager transactionManager(DataSource dataSource) {
       return new DataSourceTransactionManager(dataSource);
      }
      
  • 方法传播特性不支持事务

     @Transactional(propagation = Propagation.REQUIRED)
     public void submitOrder(){
      this.updateProductStockCountById(1, 1L);
     }
     @Transactional(propagation = Propagation.NOT_SUPPORTED)
     public void updateProductStockCountById(Integer stockCount, Long id){
      productDao.updateProductStockCountById(stockCount, id);
     }
    }
    
  • 不正确捕获异常:a事务,没有捕获异常,b事务捕获了,a调用b,b回滚了,a没有回滚

  • 错误标志异常类型:这是因为Spring中对于默认回滚的事务异常类型为RuntimeException,默认情况下,Spring事务中无法捕获到Exception异常,此时可以手动指定updateProductStockCountById()方法标注的事务异常类型

    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateProductStockCountById(Integer stockCount, Long id){
     try{
      productDao.updateProductStockCountById(stockCount, id);
     }catch(Exception e){
      logger.error("扣减库存异常:", e.getMesaage());
      throw new Exception("扣减库存异常");
     }
    }
    
Q:全局异常怎么捕获?

处理办法通过@ControllerAdvice来进行统一异常处理,@ExceptionHandler({ PayException.class })来指定需要捕获的各个类型的异常。这个异常护理是全局的,所有类似的异常,都会跑到这个方法里处理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值