zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    MySQL(一)序
    Mockito 小结
    如何入门一个开源软件
    面经
    琐碎的想法(四)键盘布局、快捷键的由来
    琐碎的想法(二)网络协议——人们给计算机的一组“约定”
    Java源码赏析(六)Class<T> 类
    Java随谈(五)Java常见的语法糖
    Java随谈(四)JDK对并发的支持
    Event Loop和宏任务微任务
  • 原文地址:https://www.cnblogs.com/1ning/p/6734189.html
Copyright © 2011-2022 走看看