一、你了解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、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
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的定义解析成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方法的代码逻辑【未】
-
通过transformedBeanName方法将名称转换成beanName
- 从缓存中加载实例
- 实例化Bean
- 检测parentBeanFactory
- 初始化依赖的Bean(递归的解决)
- 创建Bean,然后返回
Spring中默认返回的都是单例,所以用getBean返回的都是同一个对象
Spring源码解析:Bean实例的创建与初始化
1.4 依赖注入的两种方式:
- 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文件。