文章目录
Spring和Mybatis的面试总结
1 基本概念
1.1 控制反转 IOC
1.1.1 原理
IOC是一种编程思想,由主动的编程变成被动的接收,最终实现直接修改xml文件就可以实现不同的操作
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用 Spring后,对象是由 Spring来创建的
反转:程序本身不创建对象,而变成被动的接收对象
依赖注入:就是利用set方法来进行注入的。
// 1. 以前如果我们需要在一个方法之中引用另一个类。
private UserDao userDao = new UserDaoImpl();//硬编码写死
//2.当用户的需求发生变化时,程序员就需要去修改new的对象,改变原来的代码。
// IOC控制反转的核心思想就是 通过set的方法,将需要的对象注入,实现不修改源代码的时候,让代码功能随用户需求而自动改变,实现代码之间的解耦
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
1.1.2 实现原理
工厂模式+ 反射机制
把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言提供的反射机制,根据配置文件中给出的类名生成相应的对象。
什么是工厂模式:
工厂设计模式,就是说建立一个工厂类,对实现了同一接口的子类们进行统一的实例创建方法,用来提供给用户调用.而用户无需去了解这些对象该如何创建以及如何组织.
1、抽象接口(酒)——接口的实现类(白酒、红酒、啤酒)
2、创建工厂,生成基于给定信息的实体类的对象,里面实现逻辑,但是向外界屏蔽了里面具体生产的过程
- 如果A,创建返回白酒
- 如果B,创建返回红酒
- 如果C,创建返回啤酒
3、使用工厂,通过传递类型信息来获取实体类的对象。传值A,得到白酒类,这个传值可以通过反射来传
public class Test {
public static void main(String[] args) {
//1. 创建容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.从容器中获取user对象
User user = (User) applicationContext.getBean("user");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd ">
<!--
class属性:被管理对象的完整类名
name属性:获得对象时根据name获得 尽量使用name属性
-->
<bean name="user" class="com.java.User" ></bean>
</beans>
1.1.3 IOC容器
IOC中的Bean管理,Bean就相当于对象,IOC容器中存放着Spring通过注入创建的对象,然后我们再通过getBean把我们需要的对象取出来,实现了对象的创建。 IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
什么时候创建了?
单例:在启动spring容器的时候创建对象,ApplicationContext 启动过程中,会负责创建实例 Bean,往各个 Bean 中注入依赖等。
在多例的情况下,getBean时才创建对象
BeanFactory和ApplicationContext
BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能
![image-20211005194729755](https://gitee.com/shangmo_1111/my-csdnimage/raw/master/img/202110051947982.png)
![image-20211005194742935](https://gitee.com/shangmo_1111/my-csdnimage/raw/master/img/202110051947236.png)
1.1.4 Spring的IOC注入方式
分为 基于xml的注入
set
构造器:index、bytype、name
工厂注入:静态工厂、实例工厂
基于注解的注入
二、依赖注入 DI
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
1.构造器注入
2.set注入
复杂类的属性注入,list、map、null、set。。。
3.命名空间注入
|-- 导入xml约束
xmlns:p=“http://www.springframework.org/schema/p”
xmlns:c=“http://www.springframework.org/schema/c”
|-- p命名空间注入通过set注入,直接注入属性的值-》类似 property
<bean id="user" class="pojo.User" p:name="cxk" p:id="20" >
</bean>
|-- c命名空间,通过构造器注入,需要写入有参和无参构造方法-》类似 construct-args
<bean id="user2" class="pojo.User" c:name="cbh" c:id="22"></bean>
三、自动装配
Spring会在上下文自动寻找,并自动给bean装配属性
|-- 隐式的自动装配bean
byType自动装配:自动查找和自己对象set方法参数的类型相同的bean
<bean id="people" class="pojo.People" autowire="byType">
<property name="name" value="cbh"></property>
</bean>
byName自动装配:自动查找和自己对象set对应的值对应的id
|-- 使用注解实现自动装配
1.导入context约束
xmlns:context="http://www.springframework.org/schema/context"
2. 配置注解的支持:< context:annotation-config/>,或者开启扫描
3. 自动装配属性的注解方式
@Autowired是先 byteType,如果唯一则注入,否则 byName查找
@Autowired + @Qualifier = byType || byName
@resource是先byname,不符合再继续byType
public class People {
@Autowired
private Cat cat;
@Autowired
@Qualifier(value = "dog")
private Dog dog;
private String name;
}
1.2 AOP面向切面编程
不通过修改源代码方式,在主干功能里面添加新功能
1.2.1 什么是代理模式
代码业务已经实现,但是需要在原有的代码上增加新的功能,这个时候不能直接修改原有的代码。此时就通过代理的模式,相当于外面再包一层,附加上新的功能
1、核心功能:吃
2、为了扩展(增强)这个功能,前面增加洗手,后面增加收拾,再封装成新的类,就是代理类再比如卖房子:
1、房主出房子,收钱
2、但是前前后后还有很多其他事,都由中介负责。这就是代理
1.2.2 实现原理
静态代理就是一开始就写好代理类,而动态代理则是在运行过程中动态的生成代理类。AOP是通过动态代理实现的,动态代理又分为两个部分:JDK动态代理和CGLIB动态代理
Spring也就是通过这两种方式来实现AOP,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib生成一个被代理对象的子类来作为代理
- 基于接口:JDK的动态代理
- 基于类:cglib
jdk
提供了一个APIjava.lang.reflect.InvocationHandler
的类, 这个类可以让我们在JVM调用某个类的方法时动态的为些方法做些什么事。动态代理实现主要是实现
InvocationHandler
,并且将目标对象注入到代理对象proxy
中,利用反射机制来执行目标对象的方法。
public class UserDAOProxy implements InvocationHandler {
//增强谁就要传入谁的对象,这里用有参构造函数实现传值
private UserDAO userDAO = new UserDAOImpl();
public UserDAOProxy(UserDAO userDAO) {
this.userDAO = userDAO;
}
//创建代理类,返回代理对象,这个对象已经通过InvocationHandler实现增强
public UserDAO creatProxy(){
UserDAO daoProxy = (UserDAO) Proxy.newProxyInstance(userDAO.getClass().getClassLoader(),userDAO.getClass().getInterfaces(),this);
return daoProxy;
}
//InvocationHandler 重写invoke,实现功能的增强的调用
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if("add".equals(method.getName())){
System.out.println("动态代理实现方法的增强");
return method.invoke(userDAO,objects);
}
return method.invoke(userDAO,objects);
}
}
测试:
public static void main(String[] args) {
UserDAOImpl userDAO = new UserDAOImpl();
UserDAO proxy = new UserDAOProxy(userDAO).creatProxy();
proxy.add();
}
实现
所有实现
BeanPostProcessor
接口的类,在初始化bean
的时候都会调用这些类的方法,一般用于在bean
初始化前或后对bean
做一些修改。而AOP的功能实现正式基于此,在bean初始化后创建针对该bean的proxy,然后返回给用户该proxy。实际就是为bean创建一个proxy,JDKproxy或者CGLIBproxy,然后在调用bean的方法时,会通过proxy来调用bean方法
重点过程可分为:
1)通过AspectJAutoProxyBeanDefinitionParser类将AnnotationAwareAspectJAutoProxyCreator注册到Spring容器中
2)AnnotationAwareAspectJAutoProxyCreator类的postProcessAfterInitialization()方法将所有有advice的bean重新包装成proxy
3)调用bean方法时通过proxy来调用,proxy依次调用增强器的相关方法,来实现方法切入
public interface Person {
void say();
}
public class Student implements Person{
public void say(){
System.out.println("测试");
}
}
创建切面类
@Aspect
public class AspectJTest {
@Pointcut("execution(* *.say(..))")
public void test(){}
@Before("test()")
public void before(){
System.out.println("before test..");
}
@After("test()")
public void after(){
System.out.println("after test..");
}
@Around("test()")
public Object around(ProceedingJoinPoint p){
System.out.println("before1");
Object o = null;
try {
o = p.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("after1");
return o;
}
}
bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean id="student" class="test.Student"/>
<bean class="test.AspectJTest"/>
</beans>
1.2.3 相关概念
|-- 面向切面
- 连接点:类里面哪些方法可以被增强,这些方法称为连接点
- 切入点:实际被真正增强的方法,称为切入点
- 通知(增强)
(1)实际增强的逻辑部分称为通知(增强)
(2)通知有多种类型,既根据增强的逻辑位于方法的那个位置来区分
前置通知:先于被增强方法执行的方法
后置通知
环绕通知
异常通知
最终通知:在连接点正常完成后执行的通知- 切面是动作,表示整个过程
(1)把通知应用到切入点过程
1.3 spring中用到了哪些设计模式
- 工厂模式:Spring使用工厂模式可以通过
BeanFactory
或ApplicationContext
创建 bean 对象。- 单例模式: bean 的默认作用域是 singleto。Spring 通过
ConcurrentHashMap
实现单例注册表的特殊方式实现单例模式。- 代理模式:AOP功能的实现
- 模板方法模式:Spring 中
jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类。- 适配器模式:AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是
AdvisorAdapter
。BeforeAdvice
、AfterAdvice
,AfterReturningAdvice
- 观察者模式:
ApplicationListener
充当了事件监听者角色
2 Bean的生命周期
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
后置处理器,作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。
bean的使用:
Spring Bean的生命周期只有这四个阶段。逻辑在doCreate()方法中,顺序调用以下三个方法
- 实例化 Instantiation——
createBeanInstance()
- 属性赋值 Populate——
populateBean()
- 初始化 Initialization——
initializeBean()
- 销毁 Destruction——容器关闭时调用的
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 实例化阶段!
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性赋值阶段!
populateBean(beanName, mbd, instanceWrapper);
// 初始化阶段!
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
2.1 常用扩展点
2.1.1 第一大类:影响多个Bean的接口
实现了这些接口的Bean会切入到多个Bean的生命周期中。Spring内部扩展经常使用这些接口,例如自动注入以及AOP的实现
BeanPostProcessor
InstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor
作用于实例化阶段的前后,BeanPostProcessor
作用于初始化阶段的前后。
InstantiationAwareBeanPostProcessor extends BeanPostProcessor
![img](https://gitee.com/shangmo_1111/my-csdnimage/raw/master/img/202110052011331.webp)
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
try {
// 该方法的返回值会替换原本的Bean作为代理,这也是Aop等功能实现的关键点
// postProcessBeforeInstantiation方法调用点,for循环调用所有的InstantiationAwareBeanPostProcessor
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
try {
// postProcessBeforeInstantiation方法在doCreateBean方法创建Bean之前调用
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
}
2.1.2 第二大类:只调用一次的接口
- Aware类型的接口作用就是让我们能够拿到Spring容器中的一些资源。所有的Aware方法都是在初始化阶段之前调用的
- 生命周期接口
- InitializingBean 对应生命周期的初始化阶段,在
invokeInitMethods(beanName, wrappedBean, mbd);
方法中调用- DisposableBean 类似于InitializingBean,对应生命周期的销毁阶段,通过循环取所有实现了DisposableBean接口的Bean然后调用其destroy()方法 。
无所不知的Aware
- BeanNameAware
- BeanClassLoaderAware
- BeanFactoryAware
![这里写图片描述](https://gitee.com/shangmo_1111/my-csdnimage/raw/master/img/202110051734290.png)
Mybatis
1、 #{}和${}的区别是什么?
${}
是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver
。#{}
是 sql 的参数占位符,Mybatis 会将 sql 中的#{}
替换为?号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的?号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name}
的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于param.getItem().getName()
。
2、Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签?
<resultMap>
、<parameterMap>
、<sql>
、<include>
、<selectKey>
,加上动态 sql 的 9 个标签,trim|where|set|foreach|if|choose|when|otherwise|bind
等,其中为 sql 片段标签,通过<include>
标签引入 sql 片段,<selectKey>
为不支持自增的主键生成策略标签。