Java八股文-框架学习记录1


框架学习记录1

Spring框架中的单例bean是线程安全的吗?

首先Spring的bean是单例的,默认就是singleton

  • singleton:bean在每个Spring IOC容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。

bean不是线程安全的,因为id为传入的参数,临时变量。所以count不是线程安全的。可以通过多例或者加锁解决。

不过userService无状态,不会被修改。所以userService是线程安全的。

对AOP的理解

AOP是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等

我们当时在后台管理系统中,就是使用aop来记录了系统的操作日志

主要思路是这样的,使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库

Spring中事务失效的场景

1.你自己捕获了异常

原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉

解决:在catch块添加throw new RuntimeException(e)抛出

2.抛出检查异常

原因:Spring 默认只会回滚非检查异常,也就是运行时异常。(文件找不到FileNotFoundException 就属于检查异常)

解决:配置rollbackFor属性 @Transactional(roolbackFor = Exception.class)

3.非public方法导致的事务失效

原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的

解决:将Transactional注解的函数改为public函数

4.一个带事务的方法被不带事务的方法调用

不带事务的方法调用该类中带事务的方法,不会回滚。因为spring的回滚是用过代理模式生成的,如果是一个不带事务的方法调用该类的带事务的方法,直接通过this.xxx()调用,而不生成代理事务,所以事务不起作用。常见解决方法,拆类。

问题原因:JDK的动态代理。只有被动态代理直接调用时才会产生事务。在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象。而这里的this是EmployeeService真实对象而不是代理对象。

解决方法:
1.在方法A上开启事务,方法B不用事务或默认事务,并在方法A的catch中throw new RuntimeException();(在没指定rollbackFor时,默认回滚的异常为RuntimeException),这样使用的就是方法A的事务。(一定要throw new RuntimeException();否则异常被捕捉处理,同样不会回滚。)如下:

@Transactional() //开启事务
public void save(){
    try {        
        this.saveEmployee();  //这里this调用会使事务失效,数据会被保存
    }catch (Exception e){
        e.printStackTrace();
        throw new RuntimeException();
    }
}

2.方法A上可以不开启事务,方法B上开启事务,并在方法A中将this调用改成动态代理调用(AopContext.currentProxy()),如下:

public void save(){
    try {        
        EmployeeService proxy =(EmployeeService) AopContext.currentProxy();
        proxy.saveEmployee();
    }catch (Exception e){
        e.printStackTrace();
    }
}

Spring的bean的生命周期

我感觉有意思的就是他在初始化前面和初始化后面都有对应的函数,一个前置处理器,一个后置处理器。

Spring的循环引用

A依赖于B,B依赖于A

循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖

①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象

②二级缓存:缓存早期的bean对象(生命周期还没走完)

③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

先实例A对象,实例不完,把A扔到三级缓存里。然后实例B对象,B需要注入A,通过三级缓存的对象工厂获取A,并且把A存入二级缓存。B得到A了,B创建成功,B被存入一级缓存。然后A可以注入B了,A创建成功,A被存入一级缓存。清除二级缓存里的A对象。

如果是构造方法出现了循环依赖怎么解决?

由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建

SpringMVC的执行流程

  1. 用户发送出请求到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
  3. HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter(处理器适配器)
  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
  6. 方法上添加了@ResponseBody
  7. 通过HttpMessageConverter来返回结果转换为JSON并响应

SpringBoot自动配置原理

在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

其中@EnableAutoConfiguration是实现自动化配置的核心注解。

该注解通过@Import注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。

在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。

一般条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。

Spring框架的常见注解

Spring的常见注解

SpringMVC的常见注解

SpringBoot的常见注解

Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :

  • @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能;
  • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
  • @ComponentScan:Spring组件扫描

Mybatis的执行流程

①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件

②构造会话工厂SqlSessionFactory,一个项目只需要一个,单例的,一般由spring进行管理

③会话工厂创建SqlSession对象,这里面就含了执行SQL语句的所有方法

④操作数据库的接口,Executor执行器,同时负责查询缓存的维护

⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息

⑥输入参数映射

⑦输出结果映射

Mybatis的延迟加载

延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。

Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载

在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false,默认是关闭的

延迟加载的底层原理

延迟加载在底层主要使用的CGLIB动态代理完成的

第一是,使用CGLIB创建目标对象的代理对象,这里的目标对象就是开启了延迟加载的mapper

第二是, 当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,再执行sql查询

第三是,获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

Mybatis的一级、二级缓存

一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存

二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储

二级缓存默认是关闭的
开启方式,两步:
1,全局配置文件 mybatis-config.xml

<settings><setting name="cacheEnabled" value="true"/></settings>

2,映射文件 UserMapper.xml
使用标签让当前mapper生效二级缓存

注意事项:

1,对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear
2,二级缓存需要缓存的数据实现Serializable接口
3,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

Mybatis执行流程

1.加载我们的配置文件(mybatis-config.xml)和全局映射文件(UserMapper.xml).加载解析的相关信息在Configuration 对象中。

  1. SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

工厂模式对外提供sqlSessionFactory对象,建造者模式解决复杂对象的创建。

工厂应该是单例的。

  1. SqlSession sqlSession = factory.openSession();

后面通过sqlSession的一些API去做业务处理。

Mybatis中的缓存

通过装饰器模式扩展缓存的功能。

基于最普通的Cache接口,有一个普通简单的PerpetualCache实现类。我们还有LruCache,LoggingCache,SoftCache,WeakCache.这些比较复杂的装饰类。

public calss synchronizedCache implements Cache {

​ private final Cache delegate; //默认实现就是PerpetualCache

}

先走二级缓存再走一级缓存。

一级缓存是session级别的,二级缓存是sqlSessionFactory级别的,也就是进程级别。

MyBatis的涉及到的设计模式

缓存模块:装饰器模式

日志模块:适配器模式适配不同的适配器实现,interface Log,当然也体现了策略模式。代理模式

反射模块:工厂模式,装饰器模式Wrapper

sqlSesssion:建造者模式

代理模式:将 sqlSession.selectList(“com.boge.mapper.UserMapper.selectUser”)拆分为

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

mapper.selectUserById(1);

分页

在PageHelper包下,MysqlParser类中

在getPageSql函数中,sqlBuilder.append(“ limit ?,?”);

sqlSession的线程安全问题

是由Spring解决的。DefaultSqlSession是线程非安全的。

在Spring中提供了一个SqlSessionTemplate,其中每个方法都通过SqlSessionProxy来操作,这是一个动态代理对象。然后在动态代理对象中通过方法级别的DefaultSqlSession来实现相关的数据库的操作。

MyBatis的架构

接口层:sqlSession,面向开发者

核心层:具体的操作,配置解析,参数映射,sql解析,sql执行,结果集映射,插件

基础模块:支撑核心层完成一些功能,反射模块,日志模块,缓存模块,类型转换模块,数据源模块,事务模块,资源加载模块

JDBC差在哪

1.sql直接写在代码里,维护成本太高。

2.JDBC没有像Mybatis一样搞数据库连接池

3.像sql中传递参数也很麻烦,因为sql语句中where条件参数不一定,占位符和参数要一一对应。

4.对于结果集的映射也很麻烦,主要是sql本身的变化会导致解析的难度。

当实体的字段和表的字段不一致咋办?

1.在对应的sql语句中通过别名的方式

2.通过自定义resultMap标签,可以设置属性和字段的映射关系。

<resultMap id="userMap" type="User"> 
    <id property="id" column="id"></id> 
    <result property="userName" column="user_name"></result>
    
    <result property="password" column="password"></result> 
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result> 
</resultMap> 

<select id="testMohu" resultMap="userMap"> 
    select id,user_name,password,age,sex from t_user where user_name like concat('%',#{mohu},'%')
</select>

MyBatis的多个传参

@Param 注解传参法(推荐)

#{}里面的名称对应的是注解 @Param 括号里面修饰的名称。

Map 传参法(推荐)

#{}里面的名称对应的是 Map 里面的 key 名称。

public void selectUser(Map<String,Object> map);

Java Bean 传参法(推荐)

最常见的,public void selecytUser(User user); #{}的名称就是自定义对象的属性名称。


文章作者: 爱敲代码の鱼儿
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 爱敲代码の鱼儿 !
  目录