java面试总结之五

前言

  继前一篇的mybatis之后,这一篇说一说spring。spring作为框架整合的核心,是很重要的一部分,这里简单的讲一下spring的面试问题,源码部分有兴趣的可以自行深入学习,个人也建议阅读spring源码,对以后的开发会有很大的帮助,虽然我也刚开始看~

1、spring的组成

  总体来说spring分为七大模块,每块负责不同的功能,换句话说我们可以只用其中的几个部分,这也是spring的一个好处。由于在开发中未使用过struts框架,所以WEB模块这里就不再说了,其主要功能就是为了和struts这类框架整合使用。

  说到spring,我们最先想到的就是IOC(控制反转)、DI(依赖注入)等名词,这也是spring最核心的功能。总觉得这些专业词汇听起来高(gǎo)大(bù)上(dǒng),所谓的控制反转就是将依赖对象的生命周期交给spring去管理,依赖注入是指spring在合适的时机将依赖对象注入给当前对象,来段代码方便理解:

1 @Service(value = "service")
2 public class Service {
3     @Resource
4     private Dao dao;
5 }

  在Service类中,使用了Dao类,传统的方式就是通过new一个Dao实例之后使用它,而使用spring框架之后,不在需要我们手动的new对象,依赖对象Dao的实例的生命周期由spring的IOC容器管理,并且会将依赖对象的实例注入到当前类Service中,降低了两个类之间的耦合性,这也就是我们所说和控制反转和依赖注入。

  接下来说一说spring的AOP模块,通常我们用到的地方就是日志记录和事务管理两个地方,AOP意思是面向切面编程,是对面向对象编程的一个补充。想象这样一个场景:你在开发一个dubbo接口服务,自然需要记录日志,记录包括异常信息等,那么这个记录日志的代码属于你的接口吗?好像属于又好像不属于,因为日志是为了接口的状态而记录的,并不属于你的业务代码,那么调用你接口的人会为你记录日志吗?他们通常只管用就好,根本不会为你记录日志,好尴尬呀!这时候AOP就可以满足你的需求了,通过spring的AOP配置,在你的业务代码之上加入切面,记录日志,让日志代码和业务代码耦合性降低了很多。

  spring的AOP有两种实现方式,一种是使用jdk的动态代理,另一种是使用cglib,两者的区别就是jdk动态代理要求被代理类必须实现接口,而cglib则没有这个要求,jdk动态代理返回的是被代理类实现的接口的实现类,cglib返回的是被代理类的子类,对于final类cglib是无法实现的,对于final方法自然也不行。这里列出两种方式的小例子,面试的时候会问到,记住实现的接口名称。

  1 //java动态代理
  2 import java.lang.reflect.InvocationHandler;
  3 import java.lang.reflect.Method;
  4 import java.lang.reflect.Proxy;
  5 
  6 public class ProxyTest1 {
  7 
  8     public static void main(String[] args) {
  9 
 10         ProxyTest1Interface1 pt = new ProxyTest1Class1();
 11         InvocationHandler ih = new MyInvocationHandler(pt);
 12         // 两种构造方式均可
 13         ProxyTest1Interface1 test = (ProxyTest1Interface1) Proxy.newProxyInstance(pt.getClass().getClassLoader(), pt
 14                 .getClass().getInterfaces(), ih);
 15         // ProxyTest1Interface1 test = (ProxyTest1Interface1) Proxy.newProxyInstance(
 16         // ProxyTest1Class1.class.getClassLoader(), ProxyTest1Class1.class.getInterfaces(), ih);
 17         test.say("111");
 18     }
 19 }
 20 
 21 interface ProxyTest1Interface1 {
 22 
 23     void say(String str);
 24 }
 25 
 26 class ProxyTest1Class1 implements ProxyTest1Interface1 {
 27 
 28     @Override
 29     public void say(String str) {
 30 
 31         System.out.println(str);
 32     }
 33 }
 34 
 35 class MyInvocationHandler implements InvocationHandler {
 36 
 37     private Object obj;
 38 
 39     public MyInvocationHandler(Object obj) {
 40         this.obj = obj;
 41     }
 42 
 43     @Override
 44     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 45         // 此处的proxy是Proxy类型的对象,无法转换成被代理对象的实例,所以需要通过构造方法传参(cglib使用代理对象调用父类方法执行)
 46         System.out.println("--------------");
 47         method.invoke(obj, args);
 48         System.out.println("++++++++++++++");
 49         return null;
 50     }
 51 
 52 }
 53 
 54 
 55 //Cglib
 56 import java.lang.reflect.Method;
 57 
 58 import net.sf.cglib.proxy.Enhancer;
 59 import net.sf.cglib.proxy.MethodInterceptor;
 60 import net.sf.cglib.proxy.MethodProxy;
 61 
 62 public class CglibTest1 {
 63 
 64     public static void main(String[] args) {
 65 
 66         CglibProxy cp = new CglibProxy();
 67         SayHello sh = (SayHello) cp.getproxy(SayHello.class);
 68         sh.say();
 69     }
 70 }
 71 
 72 class CglibProxy implements MethodInterceptor {
 73 
 74     private Enhancer enhancer = new Enhancer();
 75 
 76     public Object getproxy(Class<?> clazz) {
 77 
 78         enhancer.setSuperclass(clazz);
 79         enhancer.setCallback(this);
 80         return enhancer.create();
 81     }
 82 
 83     // intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例
 84     @Override
 85     public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
 86 
 87         System.out.println("before....");
 88         // 通过代理类实例调用父类方法
 89         proxy.invokeSuper(obj, args);
 90         System.out.println("after....");
 91         return null;
 92     }
 93 
 94 }
 95 
 96 class SayHello {
 97 
 98     void say() {
 99         System.out.println("hello");
100     }
101 }

  再接下来就是spring的MVC模块了,spring MVC的知识点不算多,先来一张图熟悉一下结构和流程

  这张图里介绍的非常详细,包括主要的控件DispatcherServlet、HandlerMapping、HandlerAdapter和ViewResolver等,整体的流程就是:

  1)用户向服务器发送请求,请求被DispatcherServlet捕获
  2)DispatcherServlet对请求URL进行解析,获取请求资源标识符(URI),然后根据URI调用HanderMapping,获得该Handler配置的相关对象,以HandlerExecutionChain对象的方式返回,包括Handler对象本身和多个HandlerInterceptor
  3)DispatcherServlet根据返回的Handler选择一个合适的HandlerAdapter,在成功获取HandlerAdapter对象后,根据HandlerInterceptor执行拦截器的preHandler(...)方法
  4)提取request中的数据,填充Handler入参,开始执行Handler(Controller)。在填充的过程中,根据配置,spring会进行一些额外的操作:
    ①HttpMessageConvert将请求消息(如JSON,XML等)转成对象和将对象转成指定的消息
    ②数据转换,String-->Integer
    ③数据格式化,格式化日期等
    ④数据验证
  5)Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象
  6)根据返回的ModelAndView对象,选择一个合适ViewResolver
  7)ViewResolver结合model和view对象来渲染视图
  8)将渲染结果返回给客户端

   spring MVC采用前端控制器模式,即所有的请求都通过一个唯一的DispatcherServlet来处理,在Web应用系统的前端(Front)设置一个入口控制器(Controller),所有request请求都被发往此控制器统一处理。Front Controller可以用来做一个集中处理,如:页面导航、session管理、国际化等。

2、spring面试题

  1)spring用到的设计模式?

    a)监听者模式,通过在web.xml中配置ContextLoaderListener监听web服务器启动,这也是spring启动的第一步,继而初始化ContextLoader,然后创建ApplicationContext,调用同步的refresh方法,创建BeanFactory,实例化bean等一系列过程。

    b)策略模式,spring可以通过不同的方式加载配置文件,创建ApplicationContext对象。

    c)单例模式,spring的bean默认都是单例的。

    d)代理模式,在spring的AOP模块中使用。

    e)模板方法模式,在spring的DAO模块中使用,通过在代码中创建PreparedStatement对象,设置model和数据库列的对应关系。

    f)工厂模式,spring的bean都是通过BeanFactory创建的。

  暂时想到的就这么多,可能还有其他的,不过对源码的理解比较少,后续待补!

  2)spring的bean的生命周期

    a)bean被实例化之后,设置bean的属性

    b)如果bean实现了BeanNameAware接口,则执行setBeanName方法

    c)如果bean实现了BeanFactoryAware接口,则执行setBeanFactory方法

    d)如果bean实现了BeanPostProcessor接口,则执行预初始化方法

    e)如果bean实现了InitializingBean接口,则执行afterProtertiesSet方法

    f)如果bean有自定义的init-method方法,则执行此方法

    g)如果bean实现了BeanPostProcessor接口,则执行后初始化方法

    h)使用bean

    i)容器销毁

    j)如果bean实现了DisposableBean接口,则执行destroy方法

    k)如果bean有自定义的destroy-method方法,则执行此方法

  这里面没有包含BeanFactory的生命周期,BeanFactory在bean创建之前先创建,整体的流程如下图所示:

  

     

  3)spring的bean的加载过程

    1)使用resource资源对象,读取xml配置
    2)将resource对象转成document对象
    3)再根据配置将document对象转成spring的BeanDefinition对象
    4)将BeanDefinition对象注册到BeanFactory中,即放入BeanFactory的beanFactoryMap中,beanFactoryMap是一个concurrentHashMap
    注:bean经过加载、解析和注册后还不能直接使用,每个bean是相互独立的,之间没有任何联系,需要通过依赖注入将bean联系到一起。在调用getBean()方法时可以触发依赖注入,得到一个依赖关系注入完成的bean对象。
    5)先从singletonObjects(concurrentHashMap)中获取bean对象,如果有处理一下直接返回
    6)根据beanName获取BeanDefinition,并获取当前bean所有依赖的bean,对所有依赖的bean进行注册并调用getBean方法(递归),直到获取到一个没有任何依赖的bean为止(就是将所有的bean都加载一遍)
    7)判断要创建bean的类型,调用createBean方法创建bean,如果是单例的,则在之后,将bean放入singletonObjects中
    8)在createBean方法中,使用createBeanInstance方法实例化bean,使用populateBean方法为对象注入依赖

  4)spring如何实现事务的?

  这个题刚开始我也是懵逼状态,不明白啥意思,后来面试官给了点提示,问我事务在哪开启的,怎么提交或者回滚的,后来想到的是jdbc的Connection对象开启的事务并且提交或回滚,面试官的意思是spring在多线程情况下,如果保证同一线程一直使用一个Connection对象。以JdbcTemplate为例,在execute方法中通过DataSourceUtils.getConnection()获取connection,我们再看一看这个方法,发现getConnection方法中通过TransactionSynchronizationManager.getResource()获取了一个ConnectionHolder对象,再往下看就能看到端倪了,原来spring在这里做了一个小把戏,将Connection对象放入了线程的ThreadLocal中,每次getConnection时都优先从ThreadLocal中获取,这样就能保证一个线程一直使用一个Connection对象了,因为是同一个Connection对象,所以可以实现事务了。

1 public static Object getResource(Object key) {
2         Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
3         Object value = doGetResource(actualKey);
4         if (value != null && logger.isTraceEnabled()) {
5             logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
6                     Thread.currentThread().getName() + "]");
7         }
8         return value;
9 }

  这个是TransactionSynchronizationManager.getResource()方法,我们看一下里面的doGetResource(actualKey)方法,代码如下:

 1 private static Object doGetResource(Object actualKey) {
 2         Map<Object, Object> map = resources.get();
 3         if (map == null) {
 4             return null;
 5         }
 6         Object value = map.get(actualKey);
 7         // Transparently remove ResourceHolder that was marked as void...
 8         if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
 9             map.remove(actualKey);
10             // Remove entire ThreadLocal if empty...
11             if (map.isEmpty()) {
12                 resources.remove();
13             }
14             value = null;
15         }
16         return value;
17 }

  注意这里的这个resources对象,仔细一看,它其实就是一个ThreadLocal,事情也就一目了然了!

  关于spring的介绍大概就这么多,每个单位对spring的要求的是高低不一,期间遇到一家公司要求所有开源框架必须熟读源码,包括dubbo、mq等,自己的能力暂时还达不到那个高度,不过对于一些基础的东西也确实需要了解,spring的总结大概就是这么多,希望对大家有帮助,同时欢迎补充!

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接:http://www.cnblogs.com/1ning/p/6734189.html

转载于:https://www.cnblogs.com/1ning/p/6734189.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值