【剑指面试】第11篇 SpringIOC

一、你了解Spring IoC吗

1.1 IOC的概念

参考自 : [Spring框架]Spring IOC的原理及详解。

为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson提出了IOC理论,用于实现对象之间的“解耦”。

  • IoC(Inversion of Control):控制反转(是把传统上由程序代码直接操控的对象的生成交给了容器来实现, 通过容器来实现对象组件的装配和管理。所谓“控制反转”就是获得依赖对象的过程被反转了,获得依赖对象的过程由自身管理变为了由IOC容器主动注入, 由容器来创建并管理对象之间的关系。
    • 在没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
    • 在引入IOC容器之后,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
    • 通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
  • 依赖注入DI(Dependency Inversion)(IOC的别名):所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。【这是IOC的是实现方式】
  • 所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。【控制反转(Inversion of Control) 就是依赖倒置原则的一种代码设计的思路。具体采用的方法就是所谓的依赖注入(Dependency Injection)。】

1.2 IoC容器的优缺点

  • 优点:
    • 1、避免了在各处使用new创建类,并且可以做到统一的维护
    • 2、创建实例的时候不需要了解其中的细节
      • 对象的构建如果依赖非常多的对象,且层次很深,外层在构造对象时很麻烦且不一定知道如何构建这么多层次的对象。 IOC 帮我们管理对象的创建,只需要在配置文件里指定如何构建,每一个对象的配置文件都在类编写的时候指定了,所以最外层对象不需要关心深层次对象如何创建的,前人都写好了
  • 缺点:
    • 1、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
      • 现在的反射技术经过改良优化,已经非常成熟,反射方式生成对象和通常对象生成方式,速度已经相差不大了,大约为1-2倍的差距。

1.3 Spring IoC容器的技术剖析

 IOC中最基本的技术就是“反射(Reflection)”编程,反射,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。

我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的 对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

  1. spring启动时去读取Bean配置信息,并在spring容器中生成一份相应的Bean定义注册表
  2. 根据这张注册表去实例化Bean
  3. 装配Bean之间的关系,为上层提供准备就绪的运行环境

1.5 Spring IoC支持的功能

1.6 Spring IoC容器的种类

1.6.1 Spring里的几个核心的接口和类:

  • BeanDefinition:主要用来描述Bean的定义,Spring启动时会将xml和注解里的Bean的定义解析成BeanDefinition
  • BeanDefinitionRegistry:提供了向IOC 容器注册BeanDefinition对象的方法
    • spring 将bean解析成BeanDefinition后会通过BeanDefinitionRegistry类的registerBeanDefinition方法将它注册到DefaultListableBeanFactory中的beanDefinitionMap中

 

1、BeanFactory:Spring框架最核心的接口,

  • 提供了IoC的配置机制
  • 包含了Bean的各种定义,以便实例化Bean
  • 建立Bean之间的依赖关系
  • Bean生命周期的控制

 

  • ApplicationContext(应用上下文):ApplicationContext接口扩展了BeanFactory接口,它在BeanFactory基础上,又继承了多个接口来提供了一些额外的功能。继承的接口如下
    • BeanFactory:能够管理、装配Bean
    • ResourcePatternResolver:能够加载资源文件
    • MessageSource:能够实现国际化等功能
    • ApplicationEventPublisher:能够注册监听器实现监听机制

 

 

2.1 getBean方法的代码逻辑【未】

  1. 通过transformedBeanName方法将名称转换成beanName
  2. 从缓存中加载实例
  3. 实例化Bean
  4. 检测parentBeanFactory
  5. 初始化依赖的Bean(递归的解决)
  6. 创建Bean,然后返回

Spring中默认返回的都是单例,所以用getBean返回的都是同一个对象

Spring源码解析:Bean实例的创建与初始化

 

1.4 依赖注入的两种方式:

傻傻分不清:Spring IoC注入,自动装配与循环依赖

  1. setter注入: 被注入对象的setter()方法的参数列表声明依赖对象。
    1.  setter注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。
  2. 构造函数注入:被注入对象的构造方法的参数列表声明依赖对象的参数列表【在构造器上加@Autowired或者在配置文件里配置都可以】
    1. 它保证一些必要的属性在Bean实例化时就得到设置(construct是bean生命周期的第一步,实例化bean),并且确保了Bean实例在实例化后就可以使用。

 

 

三、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文件。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值