[关闭]
@nextleaf 2018-10-14T17:05:46.000000Z 字数 18496 阅读 978

2018-10-11 工作日志

Spring AOP 代理 MVC


Spring AOP

什么是AOP

AOP(Aspect Oriented Programming),即面向方面(切面)编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。
(日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。)

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

image_1cphqkaq211ttuddgs1cc11kjg1j.png-125.3kB

AOP核心概念

1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象
通知和切点是切面的最基本的元素,切面是通知和切点的结合

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

对连接点进行拦截的定义
切点和连接点之间的关系是: 切点的定义会匹配通知的一个或多个连接点. 也就是切点定义了通知被应用的位置, 即连接点

5、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,就是切面的功能, 就是切面要完成的工作. 通知定义了切面是什么以及在什么时候使用通知分为前置、后置、异常、最终、环绕通知五类

通知的类型

6、目标对象

代理的目标对象(由execution或within指定?)

7、织入(weave)

把切面应用到目标对象中,并且创建新的代理对象的过程;Spring使用的是运行时织入,AOP代理类在切点动态地织入了增强处理。
image_1cphkttepgvhgi48v9dip1egu16.png-126.3kB

8、引入(introduction)

定义:引入允许我们通过切面向现有的Spring Bean添加新方法或者新属性
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
【我们通过切面给对象中的一个方法加上了它原本没有的功能
Spring的切面由包裹了目标对象的代理实现. 代理类处理方法的调用, 执行额外的切面逻辑, 并调用目标方法. 而现在对于引入而言, 除了可以执行目标对象已有的方法(已有的接口), 还可以执行新的方法(也就是目标对象没有的方法), 即新的接口, 即底层实现类(目标对象)并没有实现的方法(接口).
我们需要注意的是, 当引入接口的方法被调用的时候, 代理会把此调用委托给实现了新接口的某个其它对象. 也就是说, 实际上是将一个Bean的实现拆分到了多个类中】

Spring对AOP的支持

两个流行的AOP框架:
Spring AOP和AspectJ(AspectJ会在编译阶段生成AOP代理类,即AspectJ是静态代理的增强)。

Spring提供了四种类型的AOP支持:

Spring基于动态代理, 所以Spring只支持方法类型的连接点(SpringAOP只能对方法进行拦截);无法在使用final修饰的bean上应用横切关注点,因为代理需要对Java类进行继承;
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。
Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

注:

Spring在使用AOP的时候,用xml还是注解的方式(@Aspect)?
1)如果使用xml方式,不需要任何额外的jar包。
2)如果使用注解的方式(@Aspect),可以在类上直接一个@Aspect就搞定,不用费事在xml里配了。但是这需要额外的jar包( aspectjweaver.jar)。因为spring直接使用AspectJ的注解功能,注意只是使用了它的注解功能而已,并不是核心功能

Spring AOP的实现原理

AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。
所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法(像环绕通知的.proceed())。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口没有声明接口,则Spring将自动使用CGLIB动态代理)。JDK动态代理的核心是InvocationHandler接口和Proxy类。
JDK动态代理伪代码:

  1. public class CostDao_proxy implements ICostDao {
  2. //用于接收注入的被代理对象
  3. private ICostDao src;
  4. public CostDao_proxy(ICostDao src) {
  5. this.src = src;
  6. }
  7. public void save() {
  8. //调用被代理对象的方法
  9. src.save();
  10. //调用方面组件逻辑......
  11. }
  12. }

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类(即使proxy-target-class设置为false)。
CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
CGLIB动态代理伪代码:

  1. public class CostDao_proxy extends CostDaoImpl {
  2. public void save() {
  3. super.save();
  4. //调用方面组件的业务逻辑
  5. }
  6. }

题外:

AspectJ和Spring AOP区别?
AspectJ在编译时就增强了目标对象,Spring AOP的动态代理则是在每次运行时动态的增强,生成AOP代理对象,区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
Spring AOP和AscpectJ之间的联系?
Spring使用了和aspectj一样的注解,并使用Aspectj来做切入点解析和匹配。但是spring AOP运行时仍旧是纯的spring AOP,并不依赖于Aspectj的编译器或者织入器,所以说在Spring中使AspectJ的注解时,AOP的实现方式还是Spring AOP。
使用哪种框架?
Spring AOP致力于提供一种能够与Spring IoC紧密集成的面向方面框架的实现,以便于解决在开发企业级项目时面临的常见问题。明确你在应用横切关注点(cross-cutting concern)时(例如事物管理、日志或性能评估),需要处理的是Spring beans还是POJO。如果正在开发新的应用,则选择Spring AOP就没有什么阻力。但是如果你正在维护一个现有的应用(该应用并没有使用Spring框架),AspectJ就将是一个自然的选择了。为了详细说明这一点,假如你正在使用Spring AOP,当你想将日志功能作为一个通知(advice)加入到你的应用中,用于追踪程序流程,那么该通知(Advice)就只能应用在Spring beans的连接点(Joinpoint)之上

Spring中主要的AOP组件

image_1cphs831v150714ic1jb01rfcgim20.png-597.2kB

Spring AOP的使用(通过配置文件)

通过xml文件来进行,大概有四种方式:

  1. 配置ProxyFactoryBean,显式地设置advisors, advice, target等
  2. 配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
  3. 通过<aop:config>来配置
  4. 通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点


通常使用第3种或第4种方式来配置Spring AOP。

步骤(使用第3种方式)

1 . 可能需要手动导入的依赖(jar包)

  1. <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
  2. <dependency>
  3. <groupId>org.aspectj</groupId>
  4. <artifactId>aspectjweaver</artifactId>
  5. <version>1.9.1</version>
  6. </dependency>

2 . AOP的使用步骤
(1) 创建方面组件
(2) 在applicationContext.xml中声明方面组件
(3) 引用方面组件

  1. <aop:config>
  2. <!--声明方面,引用方面组件-->
  3. <aop:aspect ref="方面组件id">
  4. <!--指定方面组件被谁引用,即声明切入点和通知类型-->
  5. <aop:after method="方面组件的方法名" pointcut="表达式"/>
  6. </aop:aspect>
  7. </aop:config>

例子:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/aop
  8. http://www.springframework.org/schema/aop/spring-aop.xsd">
  9. <!--class可替换为其他技术的实现类,如JDBC,Hibernate-->
  10. <bean id="costDao" class="com.nl.springpracticaltraining.dao.CostDaoImpl" scope="singleton" lazy-init="default"
  11. init-method="myinit" destroy-method="mydestroy">
  12. <!--<property name="dataSource" ref="ds"/>-->
  13. </bean>
  14. <!--Action组件使用prototype方式创建对象-->
  15. <bean id="addCostAction" class="com.nl.springpracticaltraining.action.AddCostAction" scope="prototype">
  16. <!--使用接口属性来封装,使用setter方式注入,ref依赖的组件ID-->
  17. <property name="iCostDao" ref="costDao"/>
  18. </bean>
  19. <bean id="updateCostAction" class="com.nl.springpracticaltraining.action.UpdateCostAction" scope="prototype">
  20. <!--使用接口属性来封装,使用构造方式注入,index,构造方法的参数位置,ref=依赖的组件ID-->
  21. <constructor-arg index="0" ref="costDao"/>
  22. </bean>
  23. <!--
  24. Spring AOP
  25. 通知执行顺序为: 前置通知→环绕通知→正常返回通知/异常返回通知→返回通知
  26. -->
  27. <!--方面(切面)组件声明-->
  28. <bean id="loggerBean" class="com.nl.springpracticaltraining.aspect.LoggerBean"/>
  29. <bean id="exceptionBean" class="com.nl.springpracticaltraining.aspect.ExceptionBean"/>
  30. <aop:config>
  31. <!--引用方面(切面)组件,order数字越小,优先级越高(可选)-->
  32. <aop:aspect ref="loggerBean" order="1">
  33. <!--指定方面(切面)组件被谁(pointcut表达式指定)引用,即声明通知类型和切入点(定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作)-->
  34. <aop:after method="mylogforAction" pointcut="within(com.nl.springpracticaltraining.action.*)"/>
  35. <!--AspectJ语法:https://blog.csdn.net/sunlihuo/article/details/52701548-->
  36. <aop:after method="mylogforDao" pointcut="within(com.nl.springpracticaltraining.dao.*)"/>
  37. <aop:around method="mylogforAround" pointcut="within(com.nl.springpracticaltraining.action.*)"/>
  38. </aop:aspect>
  39. <!--异常方面组件-->
  40. <aop:aspect ref="exceptionBean">
  41. <aop:after-throwing method="aVoid" throwing="e" pointcut="within(com.nl.springpracticaltraining.action.*)"/>
  42. </aop:aspect>
  43. </aop:config>
  44. </beans>

下午


使用注解实现AOP(使用第3种方式,要求必须拥有源码)

可能需要手动导入的依赖(jar包)

  1. <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
  2. <dependency>
  3. <groupId>org.aspectj</groupId>
  4. <artifactId>aspectjweaver</artifactId>
  5. <version>1.9.1</version>
  6. </dependency>

(1) 开启组件扫描

  1. <context:component-scan base-package="com.nl.springpracticaltraining"/>

(2) 开启AOP的注解配置

  1. <!--高版本Spring自动选择代理模式,无需配置proxy-target-class=""-->
  2. <aop:aspectj-autoproxy />

例如:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xmlns:tx="http://www.springframework.org/schema/tx"
  7. xsi:schemaLocation="http://www.springframework.org/schema/beans
  8. http://www.springframework.org/schema/beans/spring-beans.xsd
  9. http://www.springframework.org/schema/context
  10. http://www.springframework.org/schema/context/spring-context.xsd
  11. http://www.springframework.org/schema/aop
  12. http://www.springframework.org/schema/aop/spring-aop.xsd">
  13. <!--组件扫描-->
  14. <context:component-scan base-package="com.nl.springpracticaltraining"/>
  15. <!--
  16. 开启AOP的AspectJ注解自动代理(也可以通过JavaConfig启用自动代理)
  17. 高版本Spring自动选择代理模式,无需配置proxy-target-class=""
  18. proxy-target-class表示是否代理目标类,默认是false,也就是代理接口(JDK动态代理)即使proxy-target-class设置为false,如果目标类没有声明接口,则Spring将自动使用CGLIB动态代理
  19. proxy-target-class为true则是基于类的代理将起作用(需要cglib库)
  20. -->
  21. <!---->
  22. <aop:aspectj-autoproxy />
  23. </beans>

(3) 使用注解配置
@Component将方面组件纳入到Spring容器中,即通过注解来声明方面组件
a,使用注解声明方面组件
在类上使用@Aspect来声明该组件是一个方面组件
b,使用注解声明切入点和通知,需要在方法上通过注解声明切入点和通知类型
前置通知
@Before("within(com.qn..*)")

后置通知
@AfterReturning("within(com.qn..*)")
最终通知
@After("within(com.qn..*)")
环绕通知
@Around("within(com.qn..*)")
异常通知
@AfterThrowing(pointcut="within(com.qn..*) , throwing="e")

Spring事务

待补充

使用注解配置事务

待补充


Java代理模式

image_1cpgrs1gk10qm6io11fi1g9rjhj9.png-27.4kB

Java静态代理

真实角色与代理类同时实现一个接口,真实角色只处理与它核心业务相关的,其他的全部由代理角色完成。
(编译阶段生成代理类?)

  1. /**
  2. * 抽象接口
  3. * @author sg
  4. */
  5. public interface Subject {
  6. void printBeforeLogging();
  7. void operation();
  8. void printAfterLogging();
  9. }
  1. //真实角色
  2. public class RelSubject implements Subject{
  3. @Override
  4. public void printBeforeLogging() {
  5. System.out.println("RelSubject printBeforeLogging");
  6. }
  7. @Override
  8. public void operation() {
  9. System.out.println("RelSubject operation");
  10. }
  11. @Override
  12. public void printAfterLogging() {
  13. System.out.println("RelSubject printAfterLogging");
  14. }
  15. }
  1. //代理角色
  2. public class Proxy implements Subject{
  3. private Subject subject;
  4. public Proxy(Subject subject) {
  5. super();
  6. this.subject = subject;
  7. }
  8. @Override
  9. public void printBeforeLogging() {
  10. System.out.println("Proxy printBeforeLogging");
  11. }
  12. @Override
  13. public void operation() {
  14. //真实的角色来完成
  15. subject.operation();
  16. }
  17. @Override
  18. public void printAfterLogging() {
  19. System.out.println("Proxy printAfterLogging");
  20. }
  21. }
  1. //客户端代码
  2. public class Client {
  3. public static void main(String[] args) {
  4. Subject subject=new RelSubject();
  5. Subject proxy=new Proxy(subject);
  6. proxy.printBeforeLogging();
  7. proxy.operation();
  8. proxy.printAfterLogging();
  9. }
  10. }

Java动态代理

  1. public interface Calculate {
  2. void operate();
  3. }
  1. public class CalculateImpl implements Calculate{
  2. @Override
  3. public void operate() {
  4. System.out.println("Operating");
  5. }
  6. }
  1. //代理类
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. public class DynamicProxy implements InvocationHandler{
  6. //目标对象
  7. private Object target;
  8. public DynamicProxy(Object target){
  9. this.target= target;
  10. }
  11. public Object getProxy(){
  12. //调用Proxy的静态方法
  13. return
  14. Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
  15. target.getClass().getInterfaces(), this);
  16. }
  17. @Override
  18. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  19. Object result = method.invoke(target, args);
  20. return result;
  21. }
  22. }
  1. //客户端
  2. public class Client {
  3. public static void main(String[] args) {
  4. Calculate target=new CalculateImpl();
  5. Calculate proxy = (Calculate)new DynamicProxy(target).getProxy();
  6. proxy.operate();
  7. }
  8. }

cglib代理实现

首先下载所必须Jar包:cglib

  1. public class CglibProxyFactory implements MethodInterceptor {
  2. private Object target;
  3. public CglibProxyFactory(Object target) {
  4. super();
  5. this.target = target;
  6. }
  7. public Object myCglibLogging() {
  8. Enhancer enhancer = new Enhancer();
  9. // 生成得代理类对象为该类的子类
  10. enhancer.setSuperclass(target.getClass());
  11. enhancer.setCallback(this);
  12. return enhancer.create();
  13. }
  14. /**
  15. * 调用动态方法的时候会调用此方法
  16. */
  17. @Override
  18. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  19. Object result = method.invoke(target, args);
  20. System.out.println(method);
  21. return result;
  22. }
  23. }
  1. //测试
  2. public class CglibProxyTest {
  3. public static void main(String[] args) {
  4. CglibProxyTest ct=new CglibProxyTest();
  5. CglibProxyTest myCglibLogging = (CglibProxyTest) new CglibProxyFactory(ct).myCglibLogging();
  6. myCglibLogging.run();
  7. }
  8. public void run(){
  9. System.out.println("running");
  10. }
  11. }

cglib基于接口的代理方式的缺点是所要代理的应该实现接口,而基于cglib的代理如果定义为final也是无法实现的了。


Spring MVC

struts2工作流程

SpringMVC工作流程.png-60kB

Spring MVC工作流程(面试题)

image_1cpj3g77kaq51a2g115l1cgk88p9.png-2924.1kB
SpringMVC工作流程
Spring MVC的所有请求都会首先经过一个前端控制器DispatcherServlet.
DispatcherServlet是单实例的,并将请求委托给应用程序的普通控制器来对请求进行实质的处理.
DispatcherServlet通过查询一个或多个处理器映射(Handler mapping)来确定应该将请求发送给哪个控制器
一旦通过处理器映射选择了合适的控制器, DispatcherServlet会将请求发送给选中的控制器(又称处理器(Hadler), 实质上就是Controller组件)

到达了控制器的请求会卸下其负载(比如用户提交的信息)并等待控制器处理这些信息. (实际上设计良好的控制器本身只处理很少的甚至不处理工作, 而是将业务逻辑委托给一个或者多个服务对象进行处理.)

控制器在完成逻辑处理之后, 通常会产生一些信息, 这些信息需要返回给用户并在浏览器上显示. 这些信息被称为模型(model). 这些信息需要一用户友好的方式进行格式化(渲染), 所以信息需要发送给一个视图(view) , 通常是JSP.
控制器所做的最后一件事就是将模型数据打包, 并且标示出用于渲染输出的视图名, 也就是请求这一站需要经过模型及逻辑视图名 (4), 然后控制器将模型和视图名(ModelAndView)一同送回DispatcherServlet
对于Spring MVC而言, 返回给DispatcherServlet的视图名并不直接表示某个特定的JSP, 或者它想表示的根本就不是一个JSP, 它仅仅是传递了一个逻辑名称, 这个逻辑名称将会通过视图解析器(ViewResolver)来查找真正的结果视图
视图将使用模型数据渲染输出, 这个输出将会通过响应对象传递给客户端

另一个:
1、用户发送请求至前端控制器DispatcherServlet
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(二者组成HandlerExecutionChain),并将其一并返回给DispatcherServlet。
4、DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5、执行处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView
7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、ViewReslover解析后返回具体View
10、DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet对用户进行响应

SpringMVC与struts2的区别

1、springmvc基于方法开发的,struts2基于类开发的。springmvc将url和controller里的方法映射。映射成功后springmvc生成一个Handler对象,对象中只包括了一个method。方法执行结束,形参数据销毁。springmvc的controller开发类似web service开发。
2、springmvc可以进行单例开发,并且建议使用单例开发,struts2通过类的成员变量接收参数,无法使用单例,只能使用多例。
3、经过实际测试,struts2速度慢,在于使用struts标签,如果使用struts建议使用jstl。

Spring MVC的总体设计

DispatcherServlet类的继承关系
Spring MVC的组件

在Spring MVC框架中, 其中的3个组件是用户必须要定义和扩展的:
定义URL映射规则,
实现业务逻辑的Handler实例对象(Controller),
渲染模板资源(View)

DispatcherServlet启动时都干了哪些事?

Spring容器的创建是在FrameworkServlet的initServletBean()方法中完成的, 这个方法会创建WebApplicationContext对象, 并调用其refresh()方法来完成配置文件的加载, 配置文件的加载是先查找Servlet的init-param参数中设置的路径, 如果没有, 会根据namespace+Servlet的名称来查找XML文件. Spring容器在加载时会调用DispatcherServlet的initStrategies()方法来完成DispatcherServlet中定义的初始化工作, 也就是初始化Spring MVC的框架需要的8个组件, 这8个组件对应的8个Bean对象都保存在DispatcherServlet类中.

M-V-C对应到Spring MVC的8个核心组件

Controller设计.

Spring MVC的Controller主要是由HandlerMapping和HandlerAdapters两个组件提供

HandlerMapping负责映射用户的URL和对应的处理类, HandlerMapping并没有规定这个URL与应用的处理类如何映射, 在HandlerMapping接口中只定义了根据一个URL必须返回一个由HandlerExecutionChain代表的处理链, 我们可以在这个处理链中添加任意的HandlerAdapters实例来处理这个URL对应的请求

HandlerMapping初始化
(1) 定义一个HandlerMapping
HandlerMapping就是处理器映射,DispatcherServlet通过它将URL和Handler(控制器Controller)对应起来
Spring MVC本身提供了很多HandlerMapping的实现, 默认使用的BeanNameUrlHandlerMapping, 可以根据Bean的name属性映射到URL中. 比如我们定义这样一个Bean:

  1. <bean id="demo" name="/demo" class="WebXu.Demo">
  2. <property name="viewPage" value="/demo.html"/>
  3. </bean>
  4. <!--通过alias标签来为Bean提供多个名称, 而这些所有的名称都指向同一个Bean. -->
  5. <!--为Bean增加别名, 时为了让应用的每一个组件能更容易的对公共组件进行引用.-->
  6. <alias name="/demo" alias="/demo1"/>
  7. <alias name="/demo" alias="/demo2"/>

如果没有定义其它的HandlerMapping, Spring MVC框架则自动将/demo.html映射到WebXu.Demo这个处理类, 所有以/demo.html为alias的Bean都会被映射到这个URL中. 在Bean的定义中也支持简单的正则匹配的方式, 如/demo*会匹配所有以/demo为前缀的URL.
Spring MVC提供的几种HandlerMapping实现类基本上都是基于配置的实现方式, 也就是URL的所有匹配规则都需要我们在配置文件中进行定义

HandlerMapping的作用就是帮助我们管理URL和处理类的映射关系, 简单理解就是将一个或多个URL映射到一个或多个Spring Bean中.
(2) SimpleUrlHandlerMapping是如何将URL映射到Bean上的?
。。。

HandlerMapping的初始化工作中最重要的两个工作就是将URL与Handler的对应关系保存在HandlerMap集合中, 并将所有的interceptor对象保存在adaptedInterceptors数组中, 等到请求来临执行所有的interceptor对象.所有的interceptor对象都必须实现HandlerInterceptor接口.

HandlerAdapter初始化
HandlerMapping可以完成URL和Handler的映射关系, 那么HandlerADapter就可以帮助自定义各种Handler

Spring MVC首先帮助我们把特别的URL对应到一个Handler接口, 但是这个Handler接口类不是固定的, 也就是我们的URL对应的Handler接口可以实现多个接口, 每个接口可以定义不同的方法.

Spring MVC中提供了下面三个典型的简单HandlerAdapter实现类:

Spring MVC的HandlerAdapter机制可以让Handler的实现变得更加灵活, 不需要和其它框架一样只能和某个Handler接口绑定起来.
HandlerAdapter的初始化只是简单创建一个HandlerAdapter对象, 将这个HandlerAdapter对象保存在DisptcherServlet的handlerAdapters集合中. 当Spring MVC将某个URL对应到某个Handler时, 在handlerAdapters集合中找寻supports这个Handler的handlerAdapter对象, 然后将handlerAdapter对象返回, 并调用这个handlerAdapter接口对应的方法

如果这个handlerAdapter对象是SimpleControllerHandlerAdapter, 则调用Controller接口的public ModelAndView handler(req, resp, handler)方法.

如果用户没有自定义HandlerAdapter的实现类, Spring MVC框架将提供默认的4个HandlerAdapter实现类:
HttpRequestHandlerAdapter
SinpleServletHandlerAdapter
SimpleControllerHandlerAdapter
SimpleServletHandlerAdapter.

Controller的调用逻辑

Controller的处理逻辑的关键就是DispatcherServlet的handlerMpapings集合中根据请求的URL匹配每一个handlerMapping对象中的handler(private final Object handler), 匹配成功之后将会返回这个handler的处理, 并连接handlerExecutionChain对象. 而这个handlerExecutionChain对象中将会包含用户自定义的多个handlerInterceptor对象.
HandlerExecutionChain类的getHandler()方法是Object类型的, 所以说这里返回的Handler对象是没有类型的. Handler的类型是由HandlerAdapter决定的. DispatcherServlet会根据Handler对象在其handlerAdapter集合中匹配哪个HandlerAdapter实例来支持该Handler对象, 接下来就执行Handler对象的相应方法. 如该Handler对象的相应方法返回一个ModelAndView对象, 接下来就去执行View渲染

Controller的大致调用流程如下:

  1. 整个Spring MVC的调用是从DispatcherServlet的doervice()方法开始的, 在doService()方法中会将ApplicationContext/localeResolver/themeResolver等对象添加到request中以便于后面使用.
  2. 接着就是调用doDispatch()方法, 这个方法就用来处理用户请求.
  3. 调用checkMutipart()方法(mutipart是一种数据格式), 这个方法会检查当前的请求是否为post请求, contentType是否以mutipart/为前缀, 如果是, 将request包装成MutipartHttpRequest对象.
  4. 执行getHandler()方法.
  5. new HandlerExecutionChain(): 在handlerMappings集合中依次匹配每个HandlerMapping的getHandler(request)方法直到某个HandlerMapping的URL匹配成功, 并返回HandlerExecutionChain对象.
  6. getIntercepters():获取该Handler中定义的所有Intercepter对象.
  7. 执行HandlerIntercepter的preHandler()方法, 如果返回false, 当前请求在执行afterHandler()之后将立即返回.
  8. 执行getHandlerAdapter()方法.
  9. 得到一个HandlerAdapter对象ha, 调用ha.supports()方法, 该方法会返回在HandlerAdapters集合中第一个支持该Handler的HandlerAdapter对象

Model设计

在Controller的HandlerAdapter组件的初始化中提到HandlerAdapter接口的其中一个简单实现类:SimpleControllerHandlerAdapter, 它的handle方法会返回一个ModelAndView对象, ModelAndView对象是连接业务逻辑层与View展现层的桥梁,或者说就是连接Handler(Controller)与View的桥梁

ModelAndView对象就是持有一个ModelMap对象和一个View对象(或者View的名称).ModelMap对象就是执行模板渲染时所需要的变量对应的实例
比如我们要在JSP中使用req.getAtribute(String)获取的属性是一个对象, 那么这个对象就和它的名称一起存在ModelMap中.**
ModelMap也是一个Map, 在Handler中将模板中需要的对象存在这个Map中, 然后传递到View对应的ViewResolvers中, 不同的ViewResolvers会对这个Map中的对象由不同的处理方式.比如, JSP中将每一个ModelMap中的元素分别设置到request.setAttribute(modelName,modelValue)中

View设计

RequestToViewNameTranslator和ViewResolver组件

对于Spring MVC的View模块来说, 它由两个组件支持: RequestToViewNameTranslator和ViewResolver.

View的调用逻辑

View的大致调用流程如下:

  1. 最先调用DispatcherServlet类的getDefaultViewName()方法, 如果Handler中返回的ModelAndView对象的ViewName没有设置, 那么就会调用viewNameTranslator获取的ViewName.
  2. 调用RequestToViewNameTranslator的getViewName()方法.
  3. 调用LocaleResolver接口的resolverLocale()方法.
  4. 调用ViewResolver接口的resolverViewName方法, 返回View对象, 然后调用createView()方法, 将ViewClass属性对应的InternalResolverView实例化.
  5. 最后调用InternalResolverView的render()方法渲染出JSP页面.

一些面试题
spring mvc面试题
spring mvc面试题2


参考资料之一:
Spring AOP原理分析一次看懂(2017年08月10日)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注