zoukankan      html  css  js  c++  java
  • 面试之Spring

    一、IoC

    IoC(Inversion of Control):控制反转(是把传统上由程序代码直接操控的对象的生成交给了容器来实现, 通过容器来实现对象组件的装配和管理。所谓“控制反转”就是组件对象控制权的转移,从程序代码本身转移到了外部容器, 由容器来创建并管理对象之间的关系。),是Spring Core中最核心的部分,是通过依赖注入DI(Dependency Inversion)实现的。依赖注入的方式:

    • Setter
    • Interface
    • Constructor
    • Annotation(如@Autowired)

    IoC容器的优势

    • 避免了在各处使用new创建类,并且可以做到统一的维护
    • 创建实例的时候不需要了解其中的细节

    Spring IoC容器

    Spring 框架的核心就是Spring容器,由容器来创建对象,并将它们装配在一起,配置并管理它们的完整生命周期, Spring容器使用依赖注入来管理组成应用程序的组件。容器通过读取提供的配置元数据(该元数据可以通过XML、Java注解或Java代码提供。)来接受对象进行实例化,配置和组装的命令。

    Spring IoC支持的功能

    • 依赖注入
    • 依赖检查
    • 自动装配
    • 支持集合
    • 指定初始化方法和销毁方法
    • 支持回调方法

    Spring IoC容器的种类

    • BeanFactory
      • 提供了IoC的配置机制
      • 包含了Bean的各种定义,以便实例化Bean
      • 建立Bean之间的依赖关系
      • Bean生命周期的控制
    • ApplicationContext(应用上下文):ApplicationContext接口扩展了BeanFactory接口,它在BeanFactory基础上,又继承了多个接口来提供了一些额外的功能。继承的接口如下:
      • BeanFactory:能够管理、装配Bean
      • ResourcePatternResolver:能够加载资源文件
      • MessageSource:能够实现国际化等功能
      • ApplicationEventPublisher:能够注册监听器实现监听机制

    二、Spring Bean

    面:谈谈Spring Bean的5个作用域?

    Spring bean支持5种scope,默认是Singleton

    • Singleton:每一个Spring IoC容器中仅存在一个单实例bean
     <!--定义bean交给Spring IoC容器管理-->
        <bean id="category" class="com.yunche.spring.pojo.Category" scope="singleton">
            <!--为属性name注入初值"book"-->
            <property name="name" value="book"/>
        </bean>
    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Category c1 = (Category) context.getBean("category");
            Category c2 = (Category) context.getBean("category");
            System.out.println(c1 == c2);
        }/*Out put:
        true
        */
    }
    
    • Prototype:每次从容器中请求调用bean时,都会返回一个新的实例
    <!--定义bean交给Spring IoC容器管理-->
        <bean id="category" class="com.yunche.spring.pojo.Category" scope="prototype">
            <!--为属性name注入初值"book"-->
            <property name="name" value="book"/>
        </bean>
    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Category c1 = (Category) context.getBean("category");
            Category c2 = (Category) context.getBean("category");
            System.out.println(c1 == c2);
        }/*Out put:
        false
        */
    }
    
    • Request:每次Http请求都会创建一个新的bean
    • Session:同一个HttpSession共享同一个bean,不同的HttpSession使用不同的bean
    • GlobalSession:同一个全局Session共享一个bean

    Spring bean的生命周期

    1. Spring IoC容器找到关于bean的定义并实例化该bean
    2. Spring IoC容器对bean进行依赖注入
    3. 如果bean实现了BeanNameAware接口,则将该bean的id传给setBeanName()方法
    4. 如果bean实现了BeanFactoryAware接口,则将BeanFactory对象传给setBeanFactory()方法
    5. 如果bean实现了BeanPostProcessor接口,则调用其postProcessBeforeInitialization()方法
    6. 如果bean实现了InitializingBean接口,则调用其afterPropertySet()方法
    7. 如果有何bean管理的BeanPostProcessor对象,则这些对象的postProcessAfterInitialization()方法被调用
    8. 当销毁bean实例时,如果bean实现了DisposableBean接口,则调用其destory()方法

    三、AOP

    • AOP(面向切面编程)是关注点分离技术(不同的问题交给不同的部分去解决)的体现
    • 通用化功能代码的实现,对应的就是所谓的切面(Aspect)
    • 将业务功能和切面代码分开后,架构变得高内聚低耦合
    • 确保功能的完整性:切面最终需要被合并到业务中(Weave织入)

    AOP的三种织入方式:

    • 编译时织入:需要特殊的Java编译器,如AspectJ
    • 类加载时织入:需要特殊的Java编译器,如AspectJ和AspectWerkz
    • 运行时织入:Spring采用的方式,通过动态代理的方式,实现简单

    AOP主要名词

    • Aspect:通用功能的代码实现
    • Target:被织入Aspect的对象
    • Join Point:可以作为切入点的机会,所有方法都可以作为切入点
    • Pointcut:Aspect实际被应用在的Join Point,支持正则
    • Advice:类里的方法以及这个方法如何织入到目标方法的方式
    • Weaving:AOP的实现过程

    Advice的种类

    • 前置增强(Before)
    • 后置增强(AfterReturning)
    • 异常增强(AfterThrowing)
    • 最终增强(After)
    • 环绕增强(Around)

    面:能否写一个Spring AOP例子?

    package com.yunche.springaop.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author yunche
     * 被织入Aspect的对象--target
     * @date 2019/04/01
     */
    @RestController
    public class HelloController {
    
        @GetMapping("/hello")
        public String hello() {
            return "Hello world";
        }
    }
    
    package com.yunche.springaop.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * 切面Aspect--通用功能的实现,这里用于日志处理
     * @author yunche
     * @date 2019/04/01
     */
    @Aspect
    @Component
    public class RequestLogAspect {
        private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);
    
        //Pointcut
        @Pointcut("execution(public * com.yunche.springaop.controller..*.*(..))")
        public void webLog() {
        }
    
        //前置增强 advice
        @Before("webLog()")
        public void doBefore(JoinPoint joinPoint) {
            //使用日志记录下用户的IP和请求的URL
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            logger.info("URL: " + request.getRequestURI());
            logger.info("IP: " + request.getRemoteAddr());
        }
    
        //后置增强 advice
        @AfterReturning(returning = "ret", pointcut = "webLog()")
        public void doAfterReturning(Object ret) {
            //用于处理返回的结果
            logger.info("RESPONSE: " + ret);
        }
    }
    

    当访问http://localhost:8080/hello时,切面AOP的日志模块就会自动记录下相应的信息。

    2019-04-01 15:53:57.857  INFO 9704 --- [nio-8080-exec-4] c.y.springaop.aspect.RequestLogAspect    : URL: /hello
    2019-04-01 15:53:57.858  INFO 9704 --- [nio-8080-exec-4] c.y.springaop.aspect.RequestLogAspect    : IP: 0:0:0:0:0:0:0:1
    2019-04-01 15:53:57.858  INFO 9704 --- [nio-8080-exec-4] c.y.springaop.aspect.RequestLogAspect    : RESPONSE: Hello world
    

    面:你知道AOP是怎么实现的吗?

    答:AOP的实现有:JdkProxy和Cglib(通过修改字节码)

    • 由AOPProxyFactory根据AdvisedSupport对象的配置来决定
    • 默认策略如果目标是接口,则用JdkProxy来实现,否则用后者
    • JdkProxy的核心:InvocationHandler和Proxy类
    • Cglib:以继承的方式动态生成目标类的代理
    • JDKProxy:通过Java的内部反射机制实现
    • Cglib:借助ASM(动态修改字节码的框架)
    • 反射机制在生成类的过程中比较高效
    • ASM在生成类之后的执行过程中比较高效

    Spring里的代理模式(接口+真实实现类+代理类)的实现

    • 真实实现类的逻辑包含在了getBean方法里
    • getBean方法返回的实际上是Proxy的实例
    • Proxy实例是Spring采用JDK Proxy或CGLIB动态生成的

    面:能否写一个通过动态代理的方式实现AOP?

    package com.yunche.aop.aspect;
    
    /**
     * @ClassName: MyAspect
     * @Description: 切面
     * @author: yunche
     * @date: 2018/10/09
     */
    public class MyAspect {
    
        public void start() {
            System.out.println("模拟事务处理功能...");
        }
    
        public void end() {
            System.out.println("程序结束后执行此处...");
        }
    }
    package com.yunche.aop.jdk;
    
    /**
     * @ClassName: UserDao
     * @Description:
     * @author: yunche
     * @date: 2018/10/09
     */
    public interface UserDao {
    
        void addUser();
    }
    package com.yunche.aop.jdk;
    
    /**
     * @ClassName: UserDaoImpl
     * @Description:
     * @author: yunche
     * @date: 2018/10/09
     */
    public class UserDaoImpl implements UserDao {
        @Override
        public void addUser() {
            System.out.println("新增用户");
        }
    }
    package com.yunche.aop.jdk;
    
    import com.yunche.aop.aspect.MyAspect;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * @ClassName: JdkProxy
     * @Description: JDK代理类
     * @author: yunche
     * @date: 2018/10/09
     */
    public class JdkProxy implements InvocationHandler {
    
        /**
         * 声明目标类接口
         */
        private UserDao userDao;
    
        /**
         * 创建代理方法
         *
         * @param userDao
         * @return
         */
        public Object createProxy(UserDao userDao) {
    
            this.userDao = userDao;
    
            //1.类加载器
            ClassLoader classLoader = JdkProxy.class.getClassLoader();
    
            //2.被代理对象实现的所有接口
            Class[] clazz = userDao.getClass().getInterfaces();
    
            //3.使用代理类、进行增强,返回的是代理后的对象
            return Proxy.newProxyInstance(classLoader, clazz, this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            //声明切面
            MyAspect myAspect = new MyAspect();
    
            //指定位置程序执行前执行这个方法
            myAspect.start();
    
            //在目标类上调用方法
            Object obj = method.invoke(userDao, args);
    
            //指定位置程序执行结束后执行
            myAspect.end();
    
            return obj;
        }
    
    }
    package com.yunche.aop.test;
    
    import com.yunche.aop.jdk.JdkProxy;
    import com.yunche.aop.jdk.UserDao;
    import com.yunche.aop.jdk.UserDaoImpl;
    
    /**
     * @ClassName: JdkTest
     * @Description:
     * @author: yunche
     * @date: 2018/10/09
     */
    public class JdkTest {
    
        public static void main(String[] args) {
    
            //创建代理对象
            JdkProxy jdkProxy = new JdkProxy();
    
            //创建目标对象
            UserDao userDao = new UserDaoImpl();
    
            //从代理对象中获取增强后的目标对象
            UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
    
            //执行方法
            userDao1.addUser();
        }/*Output:
        模拟事务处理功能...
        新增用户
        程序结束后执行此处...
        */
    }
    

    四、Spring MVC

    Spring MVC的工作原理(DispatcherServlet的工作流程)

    1. 客户端的所有请求都交给前端控制器DispatcherServlet来处理,它会负责调用系统的其他模块来真正处理用户的请求。
    2. DispatcherServlet收到请求后,将根据请求的信息(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)以及HandlerMapping的配置找到 处理该请求的Handler(任何一个对象可以作为请求的Handler),最后以HandlerExecutionChain对象形式返回。
    3. DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter,它用统一的接口对各种Handler中的方法进行调用。
    4. Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServlet。
    5. DispatcherServlet借助ViewResolver完成从逻辑视图(ModelAndView是逻辑视图)到真实视图对象的解析工作。
    6. 当得到真实的视图对象后,DispatcherServlet会利用视图对象对模型数据进行渲染。
    7. 客户端得到响应,可能是一个普通的HTML页面,也可以是XML或JSON字符串,或图片、PDF文件。

    参考资料

    慕课网 剑指Java面试-Offer直通车

  • 相关阅读:
    bzoj 2483: Pku2279 Mr. Young's Picture Permutations -- 钩子公式
    bzoj 3940: [Usaco2015 Feb]Censoring -- AC自动机
    bzoj 1059: [ZJOI2007]矩阵游戏 -- 二分图匹配
    bzoj 1911: [Apio2010]特别行动队 -- 斜率优化
    bzoj 1433: [ZJOI2009]假期的宿舍 -- 最大流
    bzoj 3944: Sum -- 杜教筛
    bzoj 3872: [Poi2014]Ant colony -- 树形dp+二分
    bzoj 1115: [POI2009]石子游戏Kam -- 博弈论
    (三)shiro的认证
    (二)spring初次遇见shiro
  • 原文地址:https://www.cnblogs.com/yunche/p/10637148.html
Copyright © 2011-2022 走看看