【剑指面试】第11篇 SpringIOC

    xiaoxiao2022-07-03  194

    一、你了解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是把以前在工厂方法里写死的 对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

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

    1.5 Spring IoC支持的功能

    依赖注入依赖检查 Spring依赖检查bean 配置文件用于确定的特定类型(基本,集合或对象)的所有属性被设置4种依赖检查模式:none、simple、objects和all,默认是none none:没有依赖检查,如果bean的属性没有值的话可以不用设置。simple:对基本类型,字符型和集合进行依赖检查objects:对依赖的对象进行检查all:所有属性都检查Spring中提供了 @Required注解:用于解决只检查单个属性自动装配:自动装配是为了将依赖注入“自动化”的一个简化配置的操作。【spring2.5之后提供了注解方式的自动装配】 依赖注入的本质就是装配——自动装配:spring可以使用xml和注解来进行自动装配。自动装配就是开发人员不必知道具体要装配哪个bean的引用,这个识别的工作会由spring来完成,自动装配就是为了将依赖注入“自动化”的一个简化配置的操作【比如一个接口,你要装配它的哪一个实现类,可以由spring来自己识别完成】支持集合【LIst,Set,Map】指定初始化方法和销毁方法支持回调方法

    1.6 Spring IoC容器的种类

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

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

     

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

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

     

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

     

     

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

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

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

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

     

    1.4 依赖注入的两种方式:

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

    setter注入: 被注入对象的setter()方法的参数列表声明依赖对象。  setter注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。构造函数注入:被注入对象的构造方法的参数列表声明依赖对象的参数列表【在构造器上加@Autowired或者在配置文件里配置都可以】 它保证一些必要的属性在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的工作流程)

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

    最新回复(0)