Spring
Spring 框架中的单例 bean 是线程安全吗
不是线程安全的,Spring 框架中有一个 @Scope 注解,默认值是 singleton,单例的,
一般在 Spring 的 Bean 中注入的都是无状态的对象,没有线程安全问题,如果在 bean 中
定义了可修改的成员变量,要考虑线程安全问题,可以使用多例或者加锁来解决
1 Spring 中的 Bean 默认是单例的
对于无状态的单例 Bean 是线程安全的 对于有状态的单例 Bean 不是线程安全的(区分是否有状态,看其是否能被修改)
什么是 AOP,项目中怎么使用 AOP
概念:
AOP 称为面向切面编程,用于将与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块叫做切面,可以减少系统中的重复代码,降低模块间的耦合程度,同时提高系统的可重用性
常见 AOP 使用场景: 1.记录操作日志 2.缓存处理 3.Spring 内置的事务处理 4.公共字段的填充
1 记录操作日志(AOP 加自定义注解)
2 Spring 事务怎么实现
Spring 支持编程式事务和声明式事务
编程式事务:使用 TransactionTemplate 来实现,对业务代码有侵入性,在代码中开启事务,提交事务和回滚事务,侵入性很强
声明式事务:声明式事务建立在 AOP 中,通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中
在目标方法开始之前加入一个事务,在执行完目标方法之后**根据执行情况提交或者回滚事务**
@Transactional 就相当于一个声明式事务,其底层就是 AOP 的一个环绕通知
public Object around(Proceeding JoinPoint) throws Throwable{
try{
//开启事务
//执行业务代码
Object proceed = joinPoint.proceed();
//提交事务
return proceed;
}catch(Exception e){
e.printStackTrace();
//在抛出异常之后回滚事务
}
}
Spring 中事务失效的场景有哪些
1 异常捕获处理
事务通知只有捕捉到了目标抛出的异常,才能后续的回滚操作,如果目标自己处理掉异常,事务通知无法知悉
解决方法: 在 catch 块中添加 throw new RuntimeException(e)将异常再次抛出
2 抛出检查异常
Spring 事务默认捕获运行时异常,对于检查异常不捕获
解决方法: 配置 rollbackFor 属性 @Transactional(rollbackFor=Exception.class) 这样只要是异常就会进行捕获
3 非 Public 方法导致的事务失效
Spring 为方法创建代理,添加事务通知,前提条件都是该方法时 Public
解决方法:将修饰符改为 Public
Spring 中 Bean 的生命周期 #再看#
用途:了解 Spring 容器如何管理和创建 Bean 实例,从而方便调试和解决问题
**BeanDefinition **
通过 getBeanClassName 之后,获取到了类名就可以通过反射来创建 Bean** **
beanClassName bean 的类名
initMethodName 初始化方法名称
propertyName 初始化方法名称
scope 作用域
lazyInit 延迟初始化
Spring 容器在进行实例化时,会将 xml 配置的信息,或注解定义的 Bean 信息封装成一个 BeanDefinition 对象,Spring 根据 BeanDefinition 来
创建对象
生命周期具体过程
Bean 的创建是在构造函数处进行的,Bean 的初始化值是在下面的若干步完成的
1.得到 BeanDefinition 之后,调用 Bean 的构造函数实例化 Bean 的对象
2.接下来是实现依赖注入 有 @Value 和 @Autowire 注解的
3.Aware 接口,一个 Bean 若实现了 BeanNameAware,BeanFactoryAware,ApplicationContextAware,需要
重写方法,进一步对 Bean 进行扩展,在初始化过程中可以获取 Bean 的名称
4.BeanPostProcessor #before 后置处理器 此为再初始化方法之前进行回调
5.构造方法
两种情况 一种是 Bean 实现了 InitializingBean 接口,实现其中的初始化方法 第二种是自定义的初始化方法(在 Bean 中的某个方法中加上了 @PostConstruct 注解)(从 BeanDefinition 中读到的)
6.BeanPostProcessor #after
AOP 通常使用这个后置处理器增强的,AOP 底层使用动态代理机制,动态代理分为 JDK 动态代理和 CGLIB 动态代理
//一定要注册为组件
@Component
public class MyBeanPostProcessor implements BeanPostProcessor{
@Override
public Object postProcessBeforeInitialization(Object bean,String beanName){
.......
}
@Override
public Object postProcessAfterInitiallization(Object bean,String beanName) throws BeansException{
//可以在postProcessAfterInitiallization中创建代理对象
if(beanName.equal("user"){
System.out.println("postProcessAfterInitiallization--->user");
//cglib代理对象
Enhancer enhancer = new Enhancer();
//设置要增强的类
enhancer.setSuperclass(bean.getClass());
//执行回调方法,增强方法
enhancer.setCallBack(new InvocationHandler(){
@Override
public Object invoke(Object o,Method method,Object[] objects) throws Throwable{
//执行目标方法
return method.invoke(method,objects);
}
//创建代理对象
return enhancer.create();
}
)
}
else return bean;
}
}
public static void main(String[] args){
ApplicationContext ctx = new AnnotationConfigApplication(SpringConfig.class);
//如果在postProcessAfterInitiallization使用了代理机制,那么创建的User就是代理对象
User user = ctx.getBean(User.class);
}
7.Bean 中存在方法加入 @PreDestory,再容器关闭时会执行此方法
Spring 的循环引用问题
场景: A 依赖于 B,B 依赖于 A A 依赖于 B,B 依赖于 C,C 依赖于 A A 依赖于 A
有循环引用问题,可能出现死循环
解决方法:
通过三级缓存来实现(可以解决绝大部分的循环依赖,初始化过程中)
一级缓存 | singletonObjects | 单例池,缓存生命周期已经走完的 Bean 对象(注意此处是单例池,不存放多例对象,对于 ApplicationContext 下,单例 Bean 是立即加载的,多例 Bean 是在调用 getBean 时加载) |
---|---|---|
二级缓存 | earlySingletonObjects | 缓存早期的 bean 对象(生命周期未走完) |
三级缓存 | singletonFactories | 缓存的是 ObjectFactory,表示对象工厂,用于创建某个对象 |
一级缓存无法解决循环依赖问题,在图中没有一个 Bean 可以完整走完生命周期(存在循环依赖)
一级缓存加二级缓存可以解决普通对象的循环依赖
一级缓存加三级缓存可以解决普通或**代理对象(获得增强的对象)**的循环依赖,其中的对象工厂可以帮你创建一个代理对象
构造方法出现循环依赖(三级缓存无法解决,使用 @Lazy 注解延迟加载)
A 与 B 存在构造方法的循环依赖,三级缓存只能处理初始化数据时的循环依赖,构造函数阶段的不能处理,可以使用延迟加载
(并不是在初始化 Bean 时就依赖注入进去,而是在用到的时候再进行初始化 B)
public class A{
privte B b;
public A(@Lazy B b){
this.b=b;
}
}
public class B{
private A a;
public B(A a){
this.a=a;
}
}
SpringMVC 的执行流程
1 视图时期(老旧 JSP 时期)
所有的请求都会经过 DispatcherServlet(前端控制器,由 TomCat 初始化,调度中心),DishpatcherServlet 初始化后,会在其中加载三个组件 HandlerMapping(处理器映射器),ViewResolver(视图解析器),HandlerAdaptoor(处理器适配器)
处理器就是值得一个具体方法,处理器适配器就是对方法中的参数和返回值做出处理,例如参数中写入 @RequestBody,还有处理返回值
2 前后端分离时期(接口开发,异步)
SpringBoot 的自动配置原理
SpringBoot 默认扫描的是引导类所在的包及其子包
import 一个 AutoConfigurationImportSelector.class 表示将这个类交给 Spring 来管理,这个类会去加载 META-INF 下的 spring.factories 文件
Spring,SpringBoot,SpringMVC 的常用注解
Spring 相关
@Component,@Controller,@Service,@Repository 用于在类上实例化 Bean
@Autowired 在字段上使用,根据类型注入
@Qualifier 结合 @Autowired 使用,根据名称进行依赖注入
@Scope 标注 Bean 的作用范围
@Configuration 指定当前类为 Spring 的配置类,容器创建时会从该类加载注解
@ComponentScan 指定 Spring 在初始化容器时要扫描的包
@Bean 标注在方法上,表示该方法的返回值存储到 Spring 容器中
@Import 使用 @Import 导入的类会被 Spring 加载到 IOC 容器中
@Aspect,@Before,@After,@Around,@Ponitcut 用于切面编程
SpringMVC 相关
@RequestMapping 映射请求路径,定义在类和方法上(衍生注解 postmapping,getmapping,putmapping,deletemapping)
@RequestBody 实现接受 http 请求的 json 数据,将 json 转化为 java 对象
@RequestParam 指定请求路径的参数(传递多个参数时,且前端传递的参数与后端接收的参数不一致,可以用于映射)
@PathViriable 从请求路径中获取参数,传递给方法的形参
@ResponseBody 实现将 controller 方法返回对象转化为 json 返回到客户端
@RequestHeader 获取指定请求头的数据
@RestController @Controller+@ResponseBody
SpringBoot 相关注解
@SpringBootConfiguration 组合了 @Configuration 实现配置文件的功能
@EnableAutoConfiguration 打开自动配置功能
@ComponentScan
@SpringBootApplication
MyBatis(持久型框架)
MyBatis 的执行流程
1 需要有 MyBatis 的核心配置文件
加载映射文件时,可以使用 resource 指定特定的映射文件,也可以使用包扫描 package 的方式
2 构建 SqlSessionFactory 会话工厂,全局只有一个,可以创建 SqlSession
3 创建 SqlSession,其中包括了要执行的 sql 语句
4 Exector 执行器,真正执行数据库的操作接口,也负责维护一些缓存
5 MappedStatement 封装一些具体信息,代表某一次数据库的操作,里面存放了具体的映射文件,对应的具体方法,和 sql 语句
MyBatis 是否支持延迟加载
Mybatis 支持延迟加载,但是默认没有开启,也叫做按需加载
过程:
1 首先实体类,数据库查出数据后封装到 User 类中,但是 User 类中的 List需要查 Order 表才可以查到
2 查询数据库时,在 UserMapper 中定义了一个方法
3 那在 UserService 调用了 mapper 中的该方法,是连同嵌套的 sql 一起查还是,需要使用 OrderList
的时候再查(也就是 User 的实例对象调用 getOrderList 的时候),这就涉及到是否延迟加载的问题,
mybatis 默认是关闭的,如果想要开启有两个方法,见下图
延迟加载的原理:
MyBatis 的缓存
MyBatis 有一级和二级缓存
一级和二级缓存都是基于本地缓存 PerpetualCache(一个类),本质是一个 HashMap
一级缓存作用域是 Session 级别(SQLSession),默认是开启的,当 session 进行 flash 或 close 之后缓存中的数据会被清空
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession.getMapper(UserMapper.class);
//由于userMapper1与userMaper2处于同一个Session,第一个查询之后会将结果保存至一级缓存中
User user = userMapper1.selectById(6);
//查询相同的语句时,会直接从一级缓存中获取
User user1 = userMapper2.selectById(6);
二级缓存作用域是 namespace 和 mapper 作用域,不依赖于 SQL session,默认采用 PerpetualCache,HashMap 存储,
如果开启了二级缓存,当会话提交或关闭时,一级缓存中的数据会转移到二级缓存
当两个查询语句位于不同的 SqlSession 时,一级缓存就不起作用了,可以使用二级缓存,默认此配置时关闭的,开启需要
//全局配置文件中加入
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
// 在映射文件中加入
<cache/>
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于