写在前面
近日在知乎热门上看到有人整理了一些关于spring类的面试题目,但是并没有答案,所以我就自己抽空写了个答案,希望可以帮助到正在准备面试的朋友们,有个参考答案。也希望大家能够多多收藏转发支持!
Q:ioc是什么,有什么用?
A:Ioc全称Inversion of Control意思为控制反转,是程序设计的一种思想,或者目标。spring通过DI(依赖注入)来实现IOC,把对象的实例化过程的控制权给到了容器(spring容器);怎么理解呢?
用大白话来说就是以前一个对象产生的控制权全部是程序员自己控制的,比如产生时机、使用哪个构造方法;但是如果反转给容器则由容器决定时机和使用哪个构造方法(spring源码中有通过BeanPostProcessor来推断构造方法的)
Q:bean作用域有哪些,说一下各种使用场景?
A:bean的作用域比较多,可以参考spring官网文档对bean的scope的定义
下面给出截图
image这里的生命周期常用的就是singleton和prototype,singleton指的是单例,prototype指的是原型,spring所有创建出来的bean都是单例的,比如controller和service、dao;但是有的时候我们需要bean是一个原型的,比如在类里面有全局变量。
Q:****aop是****什么,有哪些实现方式?
A:参考spring官方文档:Aop叫做面向切面编程,在实际开发中我们的程序是一个自上而下的执行流程,比如一个登陆逻辑,用户发送HTTP请求,controller接受请求,封装参数,传给service、继而调用dao操作db然后返回。这整个逻辑当中会出现一些横切性的问题,比如进入controller的时候我们需要记录日志,比如调用dao的db操作时候需要进行事务操作,比如进入service的方法之前需要进行权限验证等等。。。这种和主逻辑无关不影响程序执行结果的问题称之为横切性问题,AOP的产生就是为了来解决这种横切性问题。 Aop编程不关心主业务逻辑,只关心这些横切性问题,比如他们的执行时机,执行的地方、执行顺序等等
Aop的实现方式可以从两个方面来回答
第一Aop的编程风格,spring提供两种编程风格,官网有解释一种是xml,一种是基于aspectj的注解风格来的
第二Aop的底层技术实现原理,spring里面提供jdk动态代理的技术和cglib的技术原理来实现AOP
Q:拦截器是什么,什么场景使用?
A:spring当中实现拦截的接口HandlerInterceptor,拦截器主要是让请求进入controller之前进行拦截处理逻辑,主要用在和相关权限的业务上
Q:bean的各种作用域是怎么样实现的?
A:工厂设计模式实现bean的作用域
Q:工具类中如何注入bean?具体使用场景?
A:通过实现Aware接口注入ApplicationContext对象,然后对外提供方位ApplicationContext对象的接口,工具类安装继而调用提供的接口获取ApplicationContext的getBean方法就能获取bean
使用场景分析:比如某个工具类A当中需要调用某个service B,但是A并不是bean,只是一个普通类,故而无法直接注入,需要用到上述方法
public static ApplicationContext applicationContext;供外部访问、得到这个对象
Q:注入的bean存在多份的时候有哪些解决办法?
A:@Quilifier可以解决,其实spring的@autowired已经非常智能了,会先根据type找,如果找到多个,在根据名字找,但是如果名字没有找到就会报错,找到了就用这个bean。
Q:****aop****里面的cglib原理是什么?
A:ASM字节码技术,动态产生一个子类的类(该子类继承了目标对象),然后实例该子类的对象,返回代理对象,完成代理
Q:aop切方法的方法的时候,哪些方法是切不了的?为什么?
A:最显而易见的便是私有方法,因为AOP的底层是代理、不管是JDK还是cglib都是不能代理私有的。很简单jdk是基于接口的,接口是没有私有的,cglib是基于继承,就是代理对象继承了目标对象,假设你的目标对象里面有一个私有方法是无法继承的,无法继承也就无法代理
Q:同类调用为什么无法切?怎么样解决(AOPContext)?
这个问题我看了很久无法看懂,估计面试官表达有问题,其实这个问题分场景,如果jdk动态代理是有问题的
参考JDK动态代理导致事务失效的原因(可以自行百度,当然你也看我这里对JDK原理的分析)
这里写个例子分析一下
比如我有接口 I
public interface I {
void f1();
void f2();
}
然后由实现类A
public class A implements I {
@Override
public void f1() {
}
@Override
public void f2() {
}
}
假设我现在产生一个代理对象(代理I接口),那么产生后的代理对象到底长什么样呢?假设你对jdk动态代理的原理比较清楚就知道JDK是通过动态产生字节码来产生代理对象的,那么我们直接把动态产生的字节码拿出来看看就知道代理对象长什么样子了。
public static void main(String[] args) throws IOException {
//获得到产生I接口的代理对象的字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
"xx", new Class[]{I.class});
//下面代码比较简单,就是把字节码写到本地
File file = new File("d:\\xx.class");
FileOutputStream fileOutputStream =new FileOutputStream(file);
fileOutputStream.write(proxyClassFile);
fileOutputStream.flush();
fileOutputStream.close();
}
现在我们已经在D盘有了一个class文件了,那么这个字节码文件我们看不懂啊,其实很简单,直接扔到idea当中(记得要扔到target下面,就是编译输出class的目录,不能放到包下面),idea反编译后的代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.spring.boot.I;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class xx extends Proxy implements I {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
public xx(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void f2() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void f1() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.spring.boot.I").getMethod("f2");
m3 = Class.forName("com.spring.boot.I").getMethod("f1");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
比如我看f1方法去掉无用的代码里面就一行代码
super.h.invoke(this, m3, (Object[])null);
这里的super就是Proxy类,这里JDK动态代理已经默认为你继承了一个类,现在你知道为什么JDK只能用接口了吧?因为java不允许多继承。
然后在Proxy类里面有一个h属性,这个h属性就是InvocatonHandler接口,是程序员提供的一个接口。在springAop的源码中:
image当然这里我先不讨论springAop;说回上面的这个InvocatonHandler接口,里面有一个invoker方法,这个方法里面的逻辑是由程序员自己提供,那么我们的代理逻辑就是写在这个方法里面,并且这个方法还负责调用目标方法的逻辑,怎么调用的呢?就是把目标对象传给这个InvocatonHandler,然后反射调用,所以你在f1里面调用f2其实是调用目标对象的f2并不是代理对象的f2。故而会导致代理失效,也是上面说的不能切本类。
Q:spring的主要扩展点有哪些?(最重要最有用的应该就是bpp了)
A:BeanFactoryPostProcessor,BeanPostProcessor,ImportBeanDefinitionRegistart等等(其实这个问题没有意义,你要结合一个主流框架如何扩展spring来说就更加有意义,比如可以参考鲁班学院讲的spring和mybatis的源码解析视频
就能看到mybatis如果扩展的spring的)
Q:有没有用过BeanFactory?场景?
A:用的不多,曾经通过BeanFactory动态往spring容器当中注入一个bean,什么意思呢?假设我一个bean最开始并不要求被spring实例化,在某个时机(某个条件成了、比如你获取了一个别人传给你的对象)需要自己实例化,实例化好之后通过BeanFactory添加到spring容器当中,代码如下:
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(Appconfig.class);
ac.getBeanFactory().registerSingleton("bname",你的对象);
Q:说说aop和ioc关系
A:其实这个题目本身意义不大,IOC和AOP都是编程目标和spring没有关系,就算没有spring也能实现AOP,比如大名鼎鼎的Aspectj技术也能实现Aop。如果硬是要说关系应该说spring的IOC和springAop有什么关系。其实意义也不大,无非springAop当中的对象必须在IOC容器当中。比如Aspectj就可以脱离IOC单独使用,但是springAop就不能脱离IOC
Q:说说DispatcherServlet做了什么
这个问题可复杂了,简单说DispatcherServlet的init方法里面load了springmvc的配置信息,然后初始化了spring容器(调用了refresh方法),把controller的信息缓存了,比如映射信息;然后DispatcherServlet会拦截所有的请求,根据用户的请求信息通过缓存的映射信息找到对应的controller的对应方法,然后反射调用(其实底层的源码就是反射调用controller的方法),然后视图裁决、解析等等工作,可以参考springMVC的工作原理去回答。
总结
近年来spring的火热可谓是Java程序员们的必备的技能来形容,光一篇文章是总结不完的,我只能抽一些比较有代表性的问题来回答。希望能够帮助到大家。
网友评论