IOC:控制反转,把传统上由程序代码直接操控的对象的调用权交给了容器,通过容器来实现对象组件的装配和管理。即对组件对象控制权的转移,从程序代码本身转移到了外部容器。一个对象依赖的其它对象会通过被动的方式传递进来。最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
AOP:面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。如果没有AOP的话,在做一些日志管理,可能需要给每个方法都加上日志处理,如果我们需要进行一些修改的逻辑,就必须得一个一个来。通过AOP的动态代理,可以在指定位置执行对应流程。这样就可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。这样的思想,被称为面向切面编程,亦即AOP。
AOP的相关概念:
依赖注入(DI)指的是组件之间的依赖关系由容器在运行期决定,即由同期动态的将某个依赖关系注入到组件之中。依赖注入的目的是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台,以及对类的解耦。
IOC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象,这一点就是通过依赖注入实现的。
例:ClassA中用到了ClassB的对象b,一般情况下,需要在A的代码中显示的new出来一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B的对象在外部new出来并注入到A来的引用中。
依赖注入与控制反转的区别是:依赖注入更倾向于实现,而控制反转更倾向于一个法则。
构造器注入、字段注入
作用域限定了Spring Bean的作用范围,在Spring配置文件定义Bean时,通过声明scope配置项,可以灵活定义Bean的作用范围。
单例Bean存在线程安全问题:当前多个线程操作同一个对象的时候,对该对象的非静态成员变量的写操作会发生线程安全问题。
解决方案:
实例化Bean:Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化。
设置Bean属性:Bean实例化后对将Bean的引入和值注入到Bean的属性中。Spring根据BeanDefinition中的信息和BeanWrapper提供的设置属性接口进行依赖注入。
注入Aware接口:检测该对象是否实现了xxxAware接口,并将相关的接口注入给Bean。
进行自定义处理:当经过上述的步骤后,Bean已经被正确的构造,如果想要对象被使用前添加一些自定义的处理,通过BeanPostProcessor接口实现。
InitializingBean与init-method
DisposableBean和destroy-method
事务:逻辑上的一组操作,组成这组操作的各个逻辑单元要么一起成功,要么一起失败。
当多个事务同时存在的时候,Spring如何处理这些事务的行为:
Spring在启动的过程中,使用到了三个map,称为三级缓存。
在启动过程的第三步(创建beanFactory并初始化所有单例bean)中,所有单例的bean初始化完成后会存放在一个Map(singletonObjects)中,beanName为key,单例bean为value。这就是一级缓存
在第三步单例bean初始化的过程如下:
bean标记为创建中
new出bean对象
如果支持循环依赖则生成三级缓存,可以提前暴露bean
填充bean属性,解决属性依赖
初始化bean,处理Aware接口并执行各类bean后处理器,执行初始化方法,如果需要生成aop代理对象
如果之前解决了aop的循环依赖,则缓存中放置了提前生成的代理对象,然后使用原始bean继续执行初始化,所以需要再返回最终bean前,把原始bean置换为代理对象返回。
此时bean已经可以被使用,进行bean注册并注册销毁方法
将bean放入容器中(一级缓存),移除创建中标记及二三级缓存
假设初始化A时,发现A依赖于B,即A初始化到了第2步,此时B还没有初始化,则需要暂停A去初始化B,那么此时需要一个可以标记A(创建中)的Map,提前的暴露正在创建的bean提供给其他的bean依赖。如果在初始化A所依赖的bean B时,发现B也需要注入一个A的依赖,则B可以从创建中的beanMap中直接获取A(创建中)对象去注入,然后完成B的初始化,返回给正在注入属性的A,最终A也完成初始化。
如果配置不允许循环依赖,就用不到上述缓存了。一般来说复杂的的业务,例如一个service中需要注入其他的service,service互相引用就会出现循环依赖。所以出现循环依赖就去解决,不出现就不解决,虽然支持循环依赖,但是只有在出现循环依赖时才真正暴露早期对象,否则只暴露一个获取bean的方法,并没有真正暴露bean,因为这个方法不会被执行到。这块的实现就是三级缓存,只缓存了一个单例bean工厂。
这个bean工厂不仅可以暴露早期bean还可以暴露代理bean,如果存在aop代理,则依赖的应该是代理对象,而不是原始的bean。
三级缓存中提到的出现循环依赖才去解决,也就是说出现循环依赖时,才会执行工厂的getObject生成(获取)早期的依赖,这个时候就需要给它换个地方存储了,因为暴露的不是工厂而是对象,所以需要使用一个新的缓存去保存暴露的早期对象(earlySingletonObjects),同时移除提前暴露的工厂,也不需要在多重循环依赖时每次去执行getObject。
Spring中对象无须自己查找或者创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋给各个对象。
作用对象不同:@Component作用于类,而@Bean作用于方法
@Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。
@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。
例如
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
等价于
<beans>
<bean id="transferService" class="com.hty.TransferServiceImpl"/>
beans>
如果说一个方法中有switch语句且其中调用了其他的方法,用@Component是无法实现的。
一般使用@Autowired注解去自动装配bean,而想要把一个类标识为可以用@Autowired注解自动装配的bean,可以采用以下的注解实现。
@Required:表明bean的属性必须在配置的时候进行配置,通过一个bean定义的显示属性值或者通过自动装配,没有则抛异常BeanInitializationException。
@Qualifier:在Controller中需要注入service,如果该service有多个实现类如何区分开这两个impl呢,就用该注解表明即可。
SpringMVC是一个基于MVC的框架,主要解决了前端页面和后台代码分离的问题,以及一个请求对应一个方法。
转发:返回值前面加forward
重定向:返回值前面加redirect
POST:配置一个CharacterEncodingFilter过滤器,设置成utf-8;
GET:修改tomcat配置文件添加编码与工程编码一致
是单例,在多线程下会有线程安全的问题。不要直接在Controller中写同步逻辑,而是在具体的Service中写,不要在Controller层中写字段。
@RequestMapping:用于处理请求url映射的注释,可用于类或者方法上。用于类上则表示类中所有的响应请求方法都是以该地址作为父路径。
@PathVariable:将URL中的占位符参数绑定到方法的参数上,例如@RequestMapping(“show5/{id}/{name}”) 用该注解可以获取。
@RequestParam:将URL中后面的参数绑定到方法的参数上,例如一url:localhost:8080/hty?name=hty 使用该注解可以获取,如果不确定后面有无参数,可以加个required = false。
@RequestBody:实现接受HTTP请求的JSON数据,用于方法参数上。只能用于@PostMapping
@ResponseBody:将controller方法对象转换为json对象响应给前端
@RestController:@ResponseBody + @Controller,即所有的方法默认返回json
@GetMapping:方法返回对应的请求是GET,POST,DELETE,PUT同理(少用)
循环依赖是什么?
Bean A 依赖 B,Bean B 依赖 A这种情况下出现循环依赖,即Bean A → Bean B → Bean A。
更复杂的间接依赖造成的循环依赖如下:Bean A → Bean B → Bean C → Bean D → Bean E → Bean A。当存在该依赖的时候,Spring加载Bean时无法决定先创建哪个Bean而产生异常。
如何解决
接口的全限定名就是映射文件中的namespace值,接口的方法名就是映射文件中Mapper的Statement的id,接口方法参数就是传给sql的参数。Mapper接口没有实现类,当调用接口方法时,接口全限定类名+方法名拼接成字符串作为key值,可以唯一定位一个MapperStatement。
在Mybatis中每一个、、、标签,都会被解析为一个MapperStatement对象。Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
MyBatis的缓存:在执行一次SQL查询或者SQL更新之后,这条SQL语句并不会消失,而是被MyBatis缓存起来,当再次执行相同的SQL语句,就会直接从缓存中进行提取,而不是再次执行SQL。其中MyBatis分一级二级缓存,一级是sqlSession级别的,二级是表级别的(一个mapper一个缓存)。
一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 sqlSession,当 Session flush 或 close 之后,该 Session 中的所有缓存清空,默认打开一级缓存。
二级缓存:多个sqlSession需要共享缓存,则需要开启二级缓存,开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。
二级缓存默认是不开启的,需要手动开启,实现二级缓存MyBatis要求返回的POJO必须是可序列化的,开启二级缓存需要在MyBatis的配置文件中进行配置:
<settings>
<setting name = "cacheEnabled" value = "true" />
settings>
开启二级缓存还需要在Mapper的XML配置文件中加入标签。
失效条件:
慎用缓存:多表操作下,在某张表中做了刷新缓存的操作,在另一张表中的缓存是仍然有效的,这样会导致查询结果不正确。
顺序传参,即#{0} #{1}
@Param注解传参法
Map传参法
Java Bean传参法:
<select id="selectUser" parameterType="com.hty.pojo.User" resultMap="UserResultMap">
一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML或者JSON格式定义。