对Spring的理解?
spring是一个框架,一个容器,还是一个生态。
控制反转(IOC)
Spring容器使用了工厂模式为我们创建了所需要的对象,我们使用时不需要自己去创建,直接调用Spring为我们提供的对象即可,这就是控制反转的思想。
依赖注入(DI)
Spring使用Java Bean对象的Set方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程就是依赖注入的基本思想。
面向切面编程(AOP)
在面向对象编程(OOP)思想中,我们将事物纵向抽象成一个个的对象。而在面向切面编程中,我们将一个个对象某些类似的方面横向抽象成一个切面,对这个切面进行一些如权限验证,事务管理,记录日志等公用操作处理的过程就是面向切面编程的思想。
Spring事务的传播机制?
Spring的事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法对事务的态度。
举例:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
在Spring中提供了7种事务的传播行为:
REQUIRED:
如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
解释:A调用B,B发现A有事务,就加入A的事务;B发现A没有事务就新建事务
REQUIRES_NEW:
新建事务,如果当前在事务中,把当前事务挂起。
SUPPORTS:
支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
NOT_SUPPORTED:
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
MANDATORY:
支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
NEVER:
以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
NESTED与REQUIRES_NEW的区别
REQUIRES_NEW是新建一个事务并且新开始的这个事务与原事务无关,而NESTED则是当前存在事务时会开启一个嵌套事务,在NESTED的情况下,父事务回滚时,子事务也会回滚。而REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务
NESTED与REQUIRED的区别
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否catch异常,事务都会回滚。而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚
事务隔离级别
spring事务隔离级别就是数据库的隔离级别,当数据库和spring代码中隔离级别不同时,以spring的配置为主。
事务隔离级别 | 中文名 | 产生的问题 | 解析 | 默认级别 |
---|---|---|---|---|
ISOLATION_READ_UNCOMMITTED | 读未提交 | 脏读、不可重复读、幻读 | 事务a,转账了,但是没有提交;事务b查询到的是转账后的数据 | Oracle默认 |
ISOLATION_READ_COMMITTED | 读已提交 | 不可重复读、幻读 | 事务a,转账了,但是没有提交;事务b查询到的是转账前的数据;事务a提交后,事务b查到的转账后数据事务a提交了事务,事务b读取到事务a提交前后的不同数据 | |
ISOLATION_REPEATABLE_READ | 可重复读 | 幻读 | 事务a读取范围数据比如age>18读取到一条数据,事务b新增数据,事务a再次读取范围数据,读取到两条数据 | MySQL默认 |
ISOLATION_SERIALIZABLE | 串行化 | 解决所有的问题 | 事务a读取过程中,事务b不能读写;所有的事务排队执行 |
Spring事务实现方式原理
在使用spring框架时,可以有两种事务的实现方式,一种是编程式事务,通过代码来控制事务的处理逻辑,一种是声明式事务,通过@Transactional注解来实现。
其实事务操作本来应该由数据库来控制,为了方便用户进行业务逻辑操作,spring对事务功能进行了扩展实现,一般我们很少用编程式事务,更多的是添加@Transactional注解来进行实现,当添加此注解之后事务的自动功能就会关闭,由spring框架来帮助进行控制。
事务操作是AOP的一个核心体现,当方法添加@Transactional注解之后,spring会基于这个类生成一个代理对象,会将这个代码对象作为bean,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务自动提交给关闭,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当然用户可以控制对哪些异常进行回滚操作。
Spring事务什么情况下会失效
1,数据库引擎不支持事务:这里以
MySQL为例,其MyISAM引擎是不支持事务操作的,InnoDB才是支持事务的引擎,一般要支持事务都会使用InnoDB。
2,bean没有被Spring管理。如果此时把 @Service注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被Spring 管理了,事务自然就失效了。
3,方法不是public的:@Transactional只能用于public的方法上,否则事务不会失效。
4,自身调用问题
@Service
public class MyService {
@Autowired
private MyRepository repository;
@Transactional
public void doTransactionalWork() {
// 这个方法会被事务管理
repository.save(someEntity);
// 内部调用,事务不生效
doNonTransactionalWork();
}
}
5,数据源没有配置事务管理器
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 启用注解驱动事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
6,异常在方法内部通过try…catch处理掉了
@Service
public class MyService {
@Autowired
private MyRepository repository;
@Transactional
public void doTransactionalWork() throws Exception {
// 这个方法会被事务管理
repository.save(someEntity);
// 抛出受检查异常,事务会回滚
throw new Exception("Some checked exception");
}
@Transactional
public void doAnotherTransactionalWork() {
try {
// 这个方法会被事务管理
repository.save(anotherEntity);
// 抛出运行时异常,事务会回滚
throw new RuntimeException("Some runtime exception");
} catch (RuntimeException e) {
// 在这里捕获了异常,事务不会回滚
// 可能导致事务失效
// 可以在这里记录日志或者进行其他处理
}
}
}
7,异常类型错误:事务默认回滚的是:RuntimeException
Spring框架中的单例bean是线程安全的吗?
Spring中的bean对象默认是单例的,Spring框架并没有对单例bean进行任何多线程的封装处理。
如果bean是有状态的,就需要开发者来保证线程安全,最浅显的解决办法就是将bean的作用域由“singleton”变更为“prototype”,这样每次请求bean对象就相当于是创建新的对象来保证线程安全。
有状态就是有数据存储的功能,无状态就是不会存储数据,我们的controller、service、dao本身并不是线程安全的,只是调用里面的方法,那么多线程调用一个实例方法,会在内存中复制遍历,这时自己线程的工作内存,才是最安全的。
因此在进行使用时,不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,也推荐使用ThreadLocal把变量变成线程私有,如果bean的实例变量或类变量需要多个线程之间共享,就只能使用synchronized、lock、cas等实现线程安全。
//简单的服务类 UserService,它包含一个计数器属性 counter 和一个方法 incrementCounter(),每次调用该方法都会将计数器加一
public class UserService {
private int counter = 0;
public void incrementCounter() {
counter++;
}
public int getCounter() {
return counter;
}
}
//Spring 的配置文件中声明 UserService 为单例 bean
<bean id="userService" class="com.example.UserService" scope="singleton"/>
//测试类 Main,并启动多个线程来并发调用 incrementCounter() 方法
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
userService.incrementCounter();
}
});
thread.start();
}
// 等待所有线程执行完毕
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Counter value: " + userService.getCounter());
}
}
Spring中所使用的设计模式
1,工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
2,单例模式:Bean默认为单例模式
3,策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
4,代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
5,模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。RestTemplate,JmsTemplate,JpaTemplate
6,适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,MethodBeforeAdviceAdapter
7,观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。
8,桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
Spring中Bean的五大作用域
可以通过@Scope注解指定
@Scope(scopeName="prototype")
public interface UserDao{}
Singleton(单例):默认的作用域
在整个应用程序中只创建一个Bean实例。
所有对该Bean的请求都将返回同一个实例。
Bean是全局共享的,适用于无状态的Bean或者需要在多个组件之间共享数据的情况
@Component
public class CalculatorService {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
// 其他方法...
}
Prototype(原型)
每次对Bean的请求都会创建一个新的实例。
没有共享状态,适用于有状态的Bean或者需要频繁创建新实例的情况
@Component
public class ShoppingCart {
private List<String> items = new ArrayList<>();
public void addItem(String item) {
items.add(item);
}
public void removeItem(String item) {
items.remove(item);
}
public List<String> getItems() {
return items;
}
// 其他方法...
}
Request(请求)
在每个HTTP请求中创建一个新的Bean实例。
每个请求的Bean实例对于该请求是唯一的。
仅在Web应用程序的上下文中有效,适用于处理HTTP请求的控制器或服务
Session(会话)
在每个用户会话(Session)中创建一个新的Bean实例。
对于同一用户的所有请求,都将使用相同的Bean实例。
仅在Web应用程序的上下文中有效,适用于保存用户特定的数据或状态。
Global Session(全局会话)
在整个应用程序的全局会话中创建一个新的Bean实例。所有的session共享一个bean实例。
spring bean的生命周期
Spring Bean的生命周期如下图所示:
1,实例化Bean:
通过反射的方式进行对象的创建。此时的创建只是在堆空间中申请空间,属性都是默认值
2,设置对象属性(依赖注入)
实例化后的对象被封装在BeanWrapper对象中,紧接着Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。
3,处理Aware相关接口并设置相关依赖
如果对象中需要引用容器内部的对象,那么需要调用aware接口的子类方法来进行统一的设置
4,Bean的前置处理器(BeanPostProcessor)
对生成的bean对象进行前置的处理工作
5,检查是否是InitializingBean的子类来决定是否调用afterPropertiesSet方法
判断当前bean对象是否设置了InitializingBean接口,然后进行属性的设置等基本工作。
6,检查是否配置有自定义的init-method方法
如果当前Bean对象定义了初始化方法,那么在此处调用初始化方法。
7,Bean的后置处理器(BeanPostProcessor
对生成的Bean对象进行后置的处理工作。
8,注册必要的destruction相关回调接口
为了方便对象的销毁,在此处调用销毁的回调接口,方便对象进行销毁操作。