2024年java面试准备--spring篇续集(一)https://developer.aliyun.com/article/1393093
面试问题:
1.如果一个接口有多个实现类,在springboot中如何调用不同实现类中的方法
public interface Animal { //动物的叫声 public void call(); //动物吃的东西 public void eat(); } 实现类1 @Service("dogImpl") public class Dog implements Animal { @Override public void call() { System.out.println("汪汪汪......"); } @Override public void eat() { System.out.println("骨头"); } } 实现类2 @Service("catImpl") public class Cat implements Animal { @Override public void call() { System.out.println("喵喵喵......"); } @Override public void eat() { System.out.println("鱼"); } }
方法1 指明实现类的优先级
在写实现类的时候事先指明实现类的优先级,注入的时候就会使用优先级高的实现类。在调用的类中注入接口,默认使用的是Primary 标注的实现类的方法
@Service("dog") @Primary public class Dog implements Animal { ....... }
方法2 通过@Autowride和@Qualifier两个注解配合使用
在调用处使用这两个注解
@Autowired @Qualifier("dog") private Animal animal; //正常启动
注:注解@Qualifier内的值是实现类的默认名
方法3 使用@Resource注解,默认类名区分
在调用处使用此注解
@Resource(name = "dog") private Animal animal; //正常启动
注:注解@Qualifier内的值是实现类中@Service指定的名字
2.拦截器的配置
- 实现HandlerInterceptor接口的拦截器
public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception { // 在请求处理之前进行拦截处理 return true; // 返回true表示继续执行请求处理,返回false表示中断请求处理 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception { // 在请求处理之后进行拦截处理 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) throws Exception { // 在请求处理完成之后进行拦截处理 } }
@Configuration public class SpringMvcSupport extends webMvcConfigurationSupport { @Autowired private MyInterceptor myInterceptor; @verride protected void addInterceptors( InterceptorRegistry registry){ registry.addInterceptor(projectInterceptor).addPathPatterns("/路径"); } }
- 使用注解的拦截器
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { } public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception { // 判断请求方法是否有MyAnnotation注解 if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); if (method.isAnnotationPresent(MyAnnotation.class)) { // 在请求处理之前进行拦截处理 return true; // 返回true表示继续执行请求处理,返回false表示中断请求处理 } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception { // 在请求处理之后进行拦截处理 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) throws Exception { // 在请求处理完成之后进行拦截处理 } } @Controller public class MyController { @RequestMapping("/test") @MyAnnotation public String test() { // 处理请求 return "test"; } }
3.分页功能实现
@Test void testGetPage(){ IPage page = new Page( "current": 1,"size": 5); bookDao.selectPage(page, "queryWrapper": null); page.getCurrent();//获取当前页 page.getSize();//获取每页数量 page.getTotal();//获取总共数据条数 page.getPages();//获取一共多少页 page.getRecords();//获取的数据 } //配置拦截器实现分页 @Configuration public class MPConfig{ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor( )); return interceptor; } }
4.如何定义一个全局异常处理类?
想要定义一个全局异常处理类的话,我们需要在这个类上添加@ContaollerAdvice注解,然后定义一些用于捕捉不同异常类型的方法,在这些方法上添加@ExceptionHandler(value = 异常类型.class)和@ResponseBody注解,方法参数是HttpServletRequest和异常类型,然后将异常消息进行处理。
如果我们需要自定义异常的话,就写一个自定义异常类,该类需要继承一个异常接口,类属性包括final类型的连续id、错误码、错误信息,再根据需求写构造方法;
5.说出Spring或者SpringMVC中常用的5个注解
@Component 基本注解,标识一个受Spring管理的组件 @Controller 标识为一个表现层的组件 @Service 标识为一个业务层的组件 @Repository 标识为一个持久层的组件 @Autowired 自动装配 @Qualifier("") 具体指定要装配的组件的id值 @RequestMapping() 完成请求映射 @PathVariable 映射请求URL中占位符到请求处理方法的形参
6.简述SpringMVC中如何返回JSON数据
Step1:在项目中加入json转换的依赖,例如jackson,fastjson,gson等
Step2:在请求处理方法中将返回值改为具体返回的数据的类型, 例如数据的集合类List等
Step3:在请求处理方法上使用@ResponseBody注解
7.Spring循环依赖问题
常见问法
请解释一下spring中的三级缓存
三级缓存分别是什么?三个Map有什么异同?
什么是循环依赖?请你谈谈?看过spring源码吗?
如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?
多例的情况下,循环依赖问题为什么无法解决?
什么是循环依赖?
循环依赖就是在创建 A 实例的时候里面包含着 B 属性实例,所以这个时候就需要去创建 B 实例,而创 建 B 实例过程中也包含着 A 实例。 这样 A 实例还在创建的过程当中,所以就导致 A 和 B 实例都创建不出来。
spring通过三级缓存来解决循环依赖:
一级缓存:缓存经过完整的生命周期的Bean
二级缓存 :缓存未经过完整的生命周期的Bean
三级缓存:缓存的是ObjectFactory,其中存储了一个生成代理类的拉姆达表达式
我们在创建 A 的过程中,先将 A 放入三级缓存 ,这时要创建B,B要创建A就直接去三级缓存中查找,并且判断需不需要进行 AOP 处理,如果需要就执行拉姆达表达式得到代理对象,不需要就取出原始对象。然后将取出的对象放入二级缓存中,因为这个时候 A 还未经 过完整的生命周期所以不能放入一级缓存。这个时候其他需要依赖 A 对象的直接从二级缓存中去获取即可。当B创建完成,A 继续执行生命周期,当A完成了属性的注入后,就可以放入一级缓存了
问题1:为什么构造器注入属性无法解决循环依赖问题?
由于spring中的bean的创建过程为先实例化 再初始化(在进行对象实例化的过程中不必赋值)将实例化好的对象暴露出去,供其他对象调用,然而使用构造器注入,必须要使用构造器完成对象的初始化的操作,就会陷入死循环的状态
问题2:一级缓存能不能解决循环依赖问题? 不能
在三个级别的缓存中存储的对象是有区别的 一级缓存为完全实例化且初始化的对象 二级缓存实例化但未初始化对象 如果只有一级缓存,如果是并发操作下,就有可能取到实例化但未初始化的对象,就会出现问题
问题3:二级缓存能不能解决循环依赖问题?
理论上二级缓存可以解决循环依赖问题,但是需要注意,为什么需要在三级缓存中存储匿名内部类(ObjectFactory),原因在于 需要创建代理对象 eg:现有A类,需要生成代理对象 A是否需要进行实例化(需要) 在三级缓存中存放的是生成具体对象的一个匿名内部类,该类可能是代理类也可能是普通的对象,而使用三级缓存可以保证无论是否需要是代理对象,都可以保证使用的是同一个对象,而不会出现,一会儿使用普通bean 一会儿使用代理类