文章目录
前言
虽然说是为了面试,但是呢我们也得不断学习,先学习思想很重要,只有思想到了,技术什么的我理解用一用,整理一下怎么使用就OK了。
若是还有什么新的面试题,我会补充,也希望大家能留言共同维护
当然可以私聊,我希望能结交一些志同道合的朋友
一、面试题
1、springIOC是什么?
1、是一种思想,(Inversion of Control:控制反转)而不是一个具体的技术实现
2、IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,但并不是spring独有
3、简单理解就是在一个Map容器中(并非真的Map)存储了要被调用的类的对象,使用的时候直接用就行,不需要进行new;
2、springIOC产生的原因:背景
历史:
在类A中我们调用类B中的方法:
但是类A中存在很多方法假设10个,在没有spring的时候我们需要再每个方法中
B b = new B(); 在通过b.方法()来实现具体调用的方法,这样我们就需要写十个。
问题:
- 代码冗余:每个需要类B的方法都要重复创建实例的代码,导致相同的代码散布在多个位置。
- 维护成本高:每次修改类B的构造函数或依赖时,所有创建了类B实例的地方都可能需要更新,这增加了维护成本并且容易引入错误。
- 耦合度高:类A直接依赖于类B的具体实现,这导致两者之间的耦合度增加,如果要替换类B或其生成逻辑,会涉及到对类A的大量更改。
- 其他:在对象.方法1的时候并不会涉及到说对方法1进行修改,所以这里也算是springIOC为什么能出现的一个小原因,但是我不知道怎样描述才能更准确。
为了解决这些问题,Spring框架引入了IoC(控制反转)原则。借助于IoC容器,我们可以将对象的创建和管理从程序代码中抽象出来,交由容器负责。为了使类A能够使用类B而无需直接实例化,我们要在类A中注入一个类B的引用:
@Service
public class B {
// 类B的实现
}
@Component
public class A {
@Autowired
private B b; // Spring容器注入类B的实例
// 类A的方法可以使用注入的b引用来调用方法
}
@Service 和 @Component 注解告诉Spring容器,它们修饰的类需要被实例化并纳入Spring容器的管理。
这通常意味着将它们作为Bean进行创建和配置。
@Autowired 注解用于自动注入依赖:告诉Spring容器,在创建类A的实例时,应当自动寻找类型为B的实例,并将其注入到类A的b字段中。
如此,所有类A的方法都能重用这一个由Spring容器提供的类B实例。
另一个选择是@Resource注解,该注解通常按名称进行依赖注入,但也可以配置按类型注入。
使用注解和IoC容器的好处是,我们可以通过简单地更改配置来更换依赖的实现,而无需修改业务逻辑代码。
这降低了类之间的耦合,使应用程序的扩展、维护以及测试更加简便。
3、spring 中Bean的生命周期:
1、Bean定义的解析:
容器通过读取配置来源(如XML文件、注解或者Java配置类)来查找并加载所有的Bean定义(Bean Definitions)。
2、Bean实例化:
容器使用Java反射API创建Bean的实例(通常是通过调用默认构造函数)。
3、依赖注入:
容器为Bean实例的属性注入相应的依赖。
4、Aware接口的处理:
- setBeanName(String): 设定Bean的名称。
- BeanFactoryAware.setBeanFactory(BeanFactory): 设定BeanFactory引用。
- ApplicationContextAware.setApplicationContext(ApplicationContext): 设定ApplicationContext引用。
- 以及其他的Aware接口,如 EnvironmentAware, ResourceLoaderAware 等,容器会调用对应的setXxx方法。
5、BeanPostProcessor前置处理:
对实现了 BeanPostProcessor 接口的Bean,容器调用
postProcessBeforeInitialization(Object bean, String beanName)方法进行前置处理。
此外,BeanPostProcessors 也实现了 postProcessBeforeInstantiation() 和 postProcessAfterInstantiation() 方法,它们分别在实例化前后调用
6、初始化:
- InitializingBean.afterPropertiesSet(): 如果Bean实现了 InitializingBean 接口,则调用此方法。
- 自定义初始化方法:如果Bean定义了init-method属性,容器调用指定的自定义初始化方法。
7、BeanPostProcessor后置处理:
对实现了 BeanPostProcessor 接口的Bean,容器调用
postProcessAfterInitialization(Object bean, String beanName) 方法进行后置处理。
此外,BeanPostProcessors 也实现了
postProcessBeforeInstantiation() 和
postProcessAfterInstantiation() 方法,它们分别在实例化前后调用
8、Bean的使用:
完成上述步骤后,Bean就可以被应用程序使用了。
9、销毁前处理:
对实现了 DestructionAwareBeanPostProcessor 接口的Bean,容器调用
postProcessBeforeDestruction(Object bean, String beanName) 方法。
10、销毁处理:
DisposableBean.destroy(): 如果Bean实现了 DisposableBean 接口,则在容器关闭时调用此方法。
自定义销毁方法:如果Bean定义了destroy-method属性,容器在关闭时调用指定的自定义销毁方法。
11、容器关闭:
最后,当应用结束,容器被关闭时,所有单例Bean的清理工作将会被触发。
请注意,以上描述的是Bean的典型生命周期,但实际过程可能会因使用不同的Bean作用域(如单例、原型等)而有所差别。
在原型作用域中,Spring容器不管理一个Bean的完整生命周期,容器会创建Bean实例,进行初始化,然后将其交给客户端代码,不负责销毁。
而在单例作用域中,上述所描述的生命周期是完整的。
4、springAOP:
建议看这篇文章:动态代理——既有落地也有思想
先理解动态代理是什么在学习这个
简而言之就是:
1、动态代理
2、什么时候植入代码:编译时植入、运行时植入等等
3、怎么使用其实就是那几个注解:
看这篇文章:springAOP落地实现
5、Spring框架中的设计模式-太多了
1、单例模式(Singleton Pattern):
-
在Spring中,单例模式通过Bean的默认作用域实现。每个Spring管理的Bean默认为单例,意味着每次请求都会获得相同的实例。
-
源码体现:DefaultSingletonBeanRegistry 类中的 singletonObjects 缓存存放着Bean的单例实例。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry {
// 单例对象的高速缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 省略其他部分
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
// 省略事务代码
return singletonObject;
}
}
2、工厂模式(Factory Pattern):
Spring使用工厂模式用于Bean的创建。BeanFactory 是一个工厂接口,它定义了获取Bean的方法。
源码体现:BeanFactory 接口定义了获取Bean的操作。
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 省略其他部分
}
3、代理模式(Proxy Pattern):
-
Spring AOP使用了代理模式。它通过JDK代理或CGLIB代理在运行时创建Bean的代理,以增加切面逻辑。
-
源码体现:JdkDynamicAopProxy 类使用了JDK动态代理技术。
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
// 使用JDK代理实现AOP的相关代码
public Object getProxy(ClassLoader classLoader) {
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用具体的目标方法之前或之后加入增强的代码
}
}
4、模板方法模式(Template Method Pattern):
-
Spring中的JdbcTemplate和HibernateTemplate等类使用了模板方法模式。它们定义了算法的骨架,将算法的一些步骤延迟到子类中实现。
-
源码体现:JdbcTemplate 中的 execute 方法。
public abstract class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
// 初始化和清理代码
try {
Statement stmt = con.createStatement();
return action.doInStatement(stmt);
} finally {
// 资源释放代码
}
}
// 省略其他部分
}
5、观察者模式(Observer Pattern):
-
Spring事件处理机制使用观察者模式,允许Bean监听应用事件。
-
源码体现:ApplicationEventMulticaster 用于事件的广播。
public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener<?> listener);
void multicastEvent(ApplicationEvent event);
}
6、策略模式(Strategy Pattern):
-
Spring中资源访问策略,如资源加载Resource和资源解析ResourceLoader。Spring允许不同的策略用于不同类型的资源访问。
-
源码体现:ResourceLoader 接口和它的实现类。
public interface ResourceLoader {
Resource getResource(String location);
}
public class DefaultResourceLoader implements ResourceLoader {
public Resource getResource(String location) {
if (location.startsWith("/")) {
return new UrlResource(location);
} else {
return new FileSystemResource(location);
}
}
}
上面仅列出了Spring框架中使用的一些设计模式,并给出了简略的代码示例。在Spring源码的众多部分都可以发现这些设计模式的身影,它们是理解和扩展Spring框架的重要基础。要深入了解这些模式在Spring源码中的应用,建议直接阅读Spring源码,并结合实际的项目场景来加深理解。
spring的三级缓存:解决循环依赖问题
说明:图是我忘了在什么地方截取的了,在我的个人笔记中,今天借鉴一下,若是有人看见可以告诉我一下。侵权则请联系我,我删除
1、什么叫循环依赖?
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
流程简述:
创建A:将A放入三级缓存中,这时发现需要创建B,
创建B:这时B需要A发现三级缓存中有A,将A拿出放入二级缓存中并将三级缓存中的A删掉,
B创建完成,然后接着
再创建A:A创建完成将A放入一级缓存,将二级缓存中的A删掉。
详细一些:
1、开始创建A:
- Spring容器开始创建Bean A。
- 在Bean A的创建过程中,容器会将创建A的ObjectFactory放入三级缓存中。
2、处理A的依赖B:
- 在A的处理过程中,发现A依赖于B,因此容器开始创建Bean B。
3、开始创建B:
- Spring容器开始创建Bean B。
- 在Bean B的创建过程中,容器会将创建B的ObjectFactory放入三级缓存中。
4、处理B的依赖A:
- 在B的处理过程中,发现B依赖于A,容器尝试获取Bean A的实例。
- 容器在一级缓存中查找Bean A的实例,但此时并未找到。
- 容器向三级缓存查询,并找到了负责创建A的ObjectFactory。
- ObjectFactory负责返回A的早期引用(可能是通过代理进行的),这个早期引用随后被放入二级缓存中。
- 注:三级缓存中的ObjectFactory不会在此时删除。它将保留直到A完全创建好,并初始化完成。
5、B创建完成:
- B的依赖注入完成后,B的实例将会被放入一级缓存中。
6、继续创建A:
- A创建过程继续,完成依赖注入(注入B)。
7、A创建完成:
- 当A完全创建并完成初始化后,它将被放入一级缓存中。
- 此时A已经在二级缓存中拥有早期引用,那么一级缓存中添加完成后,该早期引用会从二级缓存移除。
- 最终当A从二级缓存移动到一级缓存后,A在三级缓存中的ObjectFactory也会被移除。
通过上述机制,Spring容器能够解决大多数单例Bean的循环依赖问题。但记住,这种处理方式仅适用于Spring中的单例Bean,且仅限于字段注入(setter注入),对于构造器注入的循环依赖,默认情况下Spring容器无法解决。
源码:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 尝试从singletonObjects缓存中获取Bean实例
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一级缓存中不存在,并且当前Bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果二级缓存中也没有,并且允许提前暴露引用
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存中获取对应的ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过ObjectFactory获取Bean的早期引用
singletonObject = singletonFactory.getObject();
// 将早期引用放入二级缓存,并从三级缓存中移除
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
spring事务:一个又真又假的命题
概念:
在计算机科学中,事务是指一些列操作的执行单元,这些操作要么全部完成,要不全部不完成,这确保了数据的完整性。事务有如下四大特效
- 原子性(Atomicity): 事务内的所有操作都是一个不可分割的工作单元,它们要么全部成功,要么全部失败回滚。
- 一致性(Consistency): 事务的执行从一个一致性状态转换到另一个一致性状态,意味着数据库中的数据将始终保持一致性规则。
- 隔离性(Isolation): 提供了事务的隔离级别,它定义了一个事务可能受其他并发事务影响的程度。
- 持久性(Durability): 一旦事务提交,则其对数据库的更改应该是永久的,即使系统发生故障也不会丢失。
Spring中的事务管理解决的问题:
- 简化事务管理:Spring提供了一致的事务管理接口,可以在不同的事务管理API(如JTA, JDBC, Hibernate等)之间进行切换而不影响业务代码。
- 声明式事务和编程式事务:声明式事务通过配置和注解就可以实现,从而使得业务代码不必与事务管理代码耦合。编程式事务则涉及到使用事务管理API编写更加灵活的事务代码,虽然灵活但代码侵入性较高。
- 事务同步:Spring事务管理保证了在一个事务上下文中的所有操作都是同步的。
- 事务的回滚:Spring能够自动回滚标记为@Transactional并在执行过程中抛出unchecked异常时的事务。
Spring事务中的概念
- 声明式事务管理:使用注解(@Transactional)或XML配置来管理事务。
- 编程式事务管理:使用TransactionTemplate或PlatformTransactionManager的API在代码中直接管理事务。
- 事务传播行为:定义了当一个事务方法被另一个事务方法调用时,事务如何传播(如:REQUIRED, REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER, NESTED)。
- 事务隔离级别:定义了一个事务可能受其他并发事务影响的程度(如: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE)。
- 回滚规则:定义了导致事务回滚的异常类型。
容易让人混淆的概念:
- 事务传播行为和隔离级别:这两个概念虽然都与事务管理的详细配置有关,但它们解决的问题不同。传播行为关注的是与其他事务的交互;隔离级别关注的是在并发条件下的数据可见性和一致性。
- 声明式事务管理和编程式事务管理:声明式事务管理隐藏了事务管理相关的代码,因此显得简洁;而编程式事务管理需要显式调用事务API,给了开发者更高的控制力。容易混淆的点在于它们的使用场景和优势。
- @Transactional注解的继承性:在类或接口上使用@Transactional注解,并不意味着它的所有子类或实现都会继承这个事务属性,实际行为可能取决于Spring版本和配置。
- Spring支持的回滚行为:默认情况下,Spring只会在遇到运行时异常(unchecked exceptions)时回滚事务,如果要在受检异常(checked exceptions)发生时也进行回滚,需要额外配置。
- 多个数据源和分布式事务:处理多个数据库连接和分布式事务时,Spring事务管理比较复杂,可能需要集成JTA或相应的分布式事务管理器。
- spring整合MySQL 其spring本身并不具有事务这个概念,它仅仅是封装了(如JDBC、Hibernate、JPA等)的事务概念,其实际上常用的也仅仅是他的原子性和持久性,当然在特殊场景下也会用到执行和隔离性,但是一般都是默认
总结
补充
Spring IOC 常用注解以及含义:
@Component:
- 含义:这个注解表明一个类将被Spring IoC容器作为组件类处理,即该类的实例可以由容器创建和管理。
- 使用:通常标注在类上,作为通用的组件标识,通常与@Autowired结合使用进行依赖注入。
- 示例:
@Component
public class MyComponent {
// ...
}
@Service:
- 含义:标注在服务层(业务逻辑层)的类上,其继承自@Component,表明这是一个服务组件。
- 使用:与@Component作用类似,但意图更明确的指向服务层组件。
- 示例:
@Service
public class MyService {
// ...
}
@Repository:
- 含义:标注在DAO(数据访问层)层的类上,用于指示类提供了数据仓库的功能,也继承自@Component。
- 使用:可以让DAO层的异常透明地转换为Spring的数据访问异常。
- 示例:
@Repository
public class MyRepository {
// ...
}
@Controller:
- 含义:标注在MVC控制器类上,通常用于处理web请求,也是@Component的一个特化。
- 使用:用在Spring MVC中,与@RequestMapping结合使用。
- 示例:
@Controller
public class MyController {
// ...
}
@Autowired:
- 含义:标注在字段、构造器或方法上,用于自动装配Bean。
- 使用:该注解让Spring完成Bean的自动注入。如果有多个相同类型的Bean,可以配合@Qualifier使用来选择具体注入哪一个。
- 示例:
@Service
public class MyService {
@Autowired
private MyRepository repository;
}
@Qualifier:
- 含义:与@Autowired结合使用,指定注入Bean的名称。
- 使用:当存在多个同类型Bean时,用于消除自动装配过程中的歧义。
- 示例:
@Autowired
@Qualifier("specificBean")
private MyInterface myBean;
@Resource:
- 含义:和@Autowired相似,用于自动装配Bean,但它是JSR-250的注解。
- 使用:默认按照名称装配,如果找不到与名称匹配的bean,则按类型装配。
- 示例:
@Resource(name = "myBean")
private MyInterface myBean;
@Bean:
- 含义:标注在方法上,表明该方法产生一个Bean,并且交给Spring容器管理。方法返回的实例会被注册为一个Bean。
- 使用:通常用在配置类(带有@Configuration注解的类)中的方法上。
- 示例:
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
@Configuration:
- 含义:标注在类上,指示一个类提供Spring框架的Bean定义信息。
- 使用:用于定义配置类,配置类可以包含一个或多个@Bean注解方法。
- 示例:
@Configuration
public class AppConfig {
// ...
}
这些注解本身体现了Spring IoC的原则以及Spring 框架中的"约定大于配置"的理念,使用者可以通过简单的注解而不是复杂的XML配置来进行Spring的依赖注入和组件声明。
Spring事务注解@Transactional的属性和属性值:
1、value(或者叫transactionManager):
- 指定使用的事务管理器bean的名字。
2、propagation: 用来设置事务的传播行为。
- Propagation.REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,就新建一个事务。这是最常见的选择。
- Propagation.SUPPORTS:如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务的方式执行。
- Propagation.MANDATORY:如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。
- Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,就把当前事务挂起。
- Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- Propagation.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则表现如同REQUIRED。
3、isolation: 定义了数据的隔离级别。
- Isolation.DEFAULT:使用底层数据存储的默认隔离级别。
- Isolation.READ_UNCOMMITTED:允许读取尚未提交的变更,可能会导致脏读,不可重复读和幻线问题。
- Isolation.READ_COMMITTED:保证一个事务修改的数据提交后才能被另一事务读取,避免脏读。
- Isolation.REPEATABLE_READ:保证在同一个事务里多次读取同样记录的结果是一致的,避免不可重复读。
- Isolation.SERIALIZABLE:所有的事务串行执行,避免脏读,不可重复读和幻读。
4、timeout: 指定强制回滚之前可以占用的时间。
- 默认值为-1(使用底层存储的默认值,通常意味着没有超时限制)。
- 设置为正整数,表示事务可以占用的最大时间(以秒为单位)。
5、readOnly: 指定当前事务是否只读。
- true:表示这个事务只读取数据但不更新数据,可以帮助数据库引擎优化事务。
- false:表示这个事务可能会进行数据更新。
6、rollbackFor: 指定哪些异常会触发事务回滚。
7、rollbackForClassName: 与rollbackFor相同,但是指定的是类的全限定名的字符串表示形式。
8、noRollbackFor: 指定哪些异常不会触发事务回滚。
9、noRollbackForClassName: 与noRollbackFor相同,但是指定的是类的全限定名的字符串表示形式。
了解@Transactional注解的这些属性和值,将有助于在开发过程中合理规划事务的边界和行为,实现有效且安全的数据操作。这些属性的组合可以解决不同事务需要之间的冲突,增强了事务管理的灵活性和功能性。
具体代码case
转账操作的例子,使其更加详尽和符合实际场景。考虑到数据库操作,我们将使用Spring Data JPA来定义实体和存储库接口。我们假设有Account实体和对应的repository用于简化数据库操作。
以下是代码示例,包含异常类、实体类、repository接口以及事务性转账服务的详细实现:
// 自定义的业务异常类,表示账户余额不足
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
// 实体类,表示账户
@Entity
public class Account {
@Id
private Long id;
private BigDecimal balance;
// 省略了getter和setter方法
public void withdraw(BigDecimal amount) throws InsufficientFundsException {
if (balance.compareTo(amount) < 0) {
throw new InsufficientFundsException("Insufficient funds");
}
balance = balance.subtract(amount);
}
public void deposit(BigDecimal amount) {
balance = balance.add(amount);
}
}
// Spring Data JPA repository,用于处理Account实体的CRUD操作
public interface AccountRepository extends JpaRepository<Account, Long> {
// 这里可以自定义一些查询方法,但基本CRUD操作已由JpaRepository提供
}
// 服务类,包含转账的业务逻辑
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
/**
* 事务性的转账操作
*/
@Transactional(rollbackFor = InsufficientFundsException.class)
public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) throws InsufficientFundsException {
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new IllegalArgumentException("Invalid fromAccountId"));
Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() -> new IllegalArgumentException("Invalid toAccountId"));
fromAccount.withdraw(amount);
toAccount.deposit(amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
}
在这个例子中,transfer方法负责处理两个账户之间的资金转移。其使用@Transactional注解来确保操作的原子性,即:
- 如果withdraw方法因账户余额不足抛出InsufficientFundsException,整个转账操作应回滚,即不会有任何变更持久化到数据库。
- 如果操作成功,即withdraw和deposit方法都没有抛出异常,那么这些变更将被保存到数据库中,并且整个事务将被成功提交。
为了演示事务失败导致的回滚,你可以在转账操作中加入一些临时逻辑,如故意抛出异常,以观察事务的回滚效果。
请注意,为了这个例子的有效执行,你还需要一个配置Spring Data JPA和数据库连接的Spring Boot应用程序,并且Account实体与您的数据库模式相匹配。
最后,我再次强调,事务的管理和回滚行为取决于你的配置和事务管理器。为了使事务回滚正常工作,你还需要在Spring配置文件中确保事务管理器被正确设置。如果使用Spring Boot,默认情况下会自动配置事务管理器。