Spring Framework,SpringMvc整理

一切从Spring开始
一、什么是spring?
Spring是一个开源框架,它是一个容器框架,用来装javabean对象(java对象),中间件框架(万能胶)可以起到连接作用。简单来说Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)容器的框架
二、Spring组成
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式
在这里插入图片描述
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • idkSpring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
    Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用。
    什么是控制反转
    就是将对象交给容器去管理,而不是传统的在你的对象内部直接控制。
  • 谁控制谁,控制什么?:传统的Java SE程序设计,我们直接在对象内部通过new进行创建对象 ,是程序主动去创建对象;而Ioc是专门有一个容器来创建这些对象,即由IOC容器来控制对象的创建;谁控制了谁?当然是Ioc容器控制了对象;控制了什么?主要控制了外部资源的获取(对象,文件等);\
  • 为什么反转,在哪反转了?:传统的应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建注入依赖对象;为何反转?由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;在哪反转了?依赖对象的获取反转了

在这里插入图片描述
图1-1 传统应用程序示意图
在这里插入图片描述
图1-2有IoC/DI容器后程序结构示意图

三、IOC和DI
DI-Dependency Injection,即“依赖注入”:组件之间的关系有容器在运行期决定,即由容器动态的将某个依赖关系注入到组件中。
 理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”。

  • 谁依赖谁:应用程序依赖于IoC容器;
  • 为什么要依赖:应用程序需要IOC来提供对象所需要的外部资源;
  • 谁注入了谁:IOC容器注入了应用程序所需要的某个对象,应用程序依赖的对象;
  • 注入了什么:注入了对象所需要的外部资源(对象,资源,常量数据);
  • IOC和DI的关系? 它们是同一个概念的不同角度描述
    Spring的IoC容器在实现控制反转和依赖注入的过程中,可以划分为两个阶段:
  • 容器启动阶段
  • Bean实例化阶段
    这两个阶段中,IoC容器分别作了以下这些事情:
    在这里插入图片描述
    在IoC模式中,被注入对象又是通过哪些方式来通知IoC容器为其提供适当服务的呢?
    常用的有两种方式:构造注入和setter注入,还有一种叫做接口注入,下面就比较一下三种注入方式。
  • 接口注入。从注入方式的使用上来说,接口注入是现在不甚提倡的一种方式,基本处于“退役状态”。因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter方法注入则不需要如此。
  • 构造注入。这种注入方式的优点就是,对象在构造完成之后就进入了就绪状态,可以马上使用缺点就是,当依赖对象较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对同类型的参数的处理会比较困难,维护和使用也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必需的依赖处理,可能需要需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。
  • setter注入。因为方法可以命名,所有setter方法注入在描述性上要比构造方法注入要好一些。另外setter方法可以被继承,运行设置默认值,而且有良好的IDE支持缺点就是对象无法在构造完成时马上进入就绪状态。这些操作都是由IoC容器来做的,我们所要做的,就是调用IoC容器来获得对象而已。
    IoC容器及IoC容器如何获取对象间的依赖关系
    Spring中提供了两种IoC容器:
  • BeanFactory
  • ApplicationContext
    这两个容器间的关系如下图:
    在这里插入图片描述
    可以看到,ApplicationContext是BeanFactory的子类,所以,ApplicationContext可以看做更强大的BeanFactory,他们两个之间的区别如下:
  • BeanFactory。基础类型容器,提供完整的IOC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管理对象的时候,才会对该受管理对象进行初始化即依赖注入操作所以相对来说,容器启动初期速度较快,所需资源有限,并且功能需求不是很严格的场景,BeanFactory是比较合适的IOC容器选择。
  • ApplicationContext。ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory所有支持,ApplicationContext还提供其他高级特性,比如事务发布、国际化信息支持等,AppactionContext所管理的对象,在该类容器启动之后,默认全部初始化并完成绑定。所有相对于BeanFactory来说,ApplictionContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较BeanFactroy也会长一些。在那些资源充足并要求更多的功能的场景中,ApplicationContext类型的容器是比较合适的选择。
但是我们无论使用哪个容器,我们都需要通过某种方法告诉容器关于对象依赖的信息,只有这样,容器才能合理的创造出对象,否则,容器自己也不知道哪个对象依赖哪个对象,如果胡乱注入,那不是创造出一个四不像。理论上将我们可以通过任何方式来告诉容器对象依赖的信息,比如我们可以通过语音告诉他,但是并没有人实现这样的代码,所以我们还是老老实实使用Spring提供的方法吧:但是我们无论使用哪个容器,我们都需要通过某种方法告诉容器关于对象依赖的信息,只有这样,容器才能合理的创造出对象,否则,容器自己也不知道哪个对象依赖哪个对象,如果胡乱注入,那不是创造出一个四不像。理论上将我们可以通过任何方式来告诉容器对象依赖的信息,比如我们可以通过语音告诉他,但是并没有人实现这样的代码,所以我们还是老老实实使用Spring提供的方法吧:
  • 通过最基本的文本文件来记录被注入对象和其依赖对象之间的对应关系
  • 通过描述性较强的xml文件格式来记录对应信息
  • 通过编写代码的方式来注入这些对应的信息
  • 通过注解方式来注册这些对应信息
    Spring Beans
  • 什么是Spring beans?
    Spring beans 是那些形成Spring应用的主干java对象。他们被Spring IOC容器初始化,装配和管理。这些beans通过容器中配置的元数据创建。比如,以xml文件中的形式定义。
    Spring框架定义的beans都是单件的beans。在bean tag中有个属性“singleton”,如果它被赋值为TRUE,bean就是单件
虽然提供了四种方式,但是我们一般只使用xml文件方式和注解方式

SpringAOP

  • 什么是AOP
    AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”简单的说就是,将一些与业务逻辑无关的关注点分离出来
    实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
  • AOP使用场景
    AOP用来封装横切关注点,具体可以在下面的场景中使用:

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging  调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence  持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

  • AOP相关概念

    切面(Aspect)::一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。

    连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

    通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

    切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上

    引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

    目标对象(Target Object):包含连接点的对象。也被称作被通知或被代理对象。POJO

    AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

    织入(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
    SpringAOP组件
    下面这种类图列出了Spring中主要的AOP组件
    在这里插入图片描述
    SpringAop中有哪些不同的通知类型
    通知类型是你在你的程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型:
    1.前置通知(Before Adivce):在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用@Before注解使用这个Advice。
    2.返回之后的通知(After Retuning Advice):在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过@AfterReturuning 关注使用它。
    3.抛出(异常)后执行通知(After Throwing Advice):如果一个方法通过抛出异常来退出会话的话,这个Advice就会执行。通过@AfterThrowing注解来使用。
    4.后置通知(After Advice):无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行这些Advice。通过使用@After注解使用。
    5.环绕通知(Around Advice):围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过@Around注解使用。

如何使用SpringAOP
可以通过配置文件或者编程方式来使用SpringAOP
配置文件可以通过xml文件来进行,大概有四种方式:
1.配置ProxyFactoryBean,显示的设置advisors,advice,target等
2. 配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
3. 通过aop:config来配置
4. 通过aop:aspectj-autoproxy来配置,使用AspectJ的注解来标识通知及切入点。也可以直接使用ProxyFactory来以编程的方式使用Spring AOP,通过ProxyFactory提供的方法可以设置target对象, advisor等相关配置,最终通过 getProxy()方法来获取代理对象
SpringAOP代理对象的生成
SpringAOP提供了两种方式来生成代理对象:JDK和Cglib,具体用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定的。默认的目标对象如果是目标类的接口,则使用JDK动态代理技术,否则使用Cglib生成代理。面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:

/**
    * <ol>
    * <li>获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised(opaque=false)
    * <li>检查上面得到的接口中有没有定义 equals或者hashcode的接口
    * <li>调用Proxy.newProxyInstance创建代理对象
    * </ol>
    */
   public Object getProxy(ClassLoader classLoader) {
       if (logger.isDebugEnabled()) {
           logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource());
       }
       Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);
       findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

那这个其实很明了,注释上我也已经写清楚了,不再赘述。

下面的问题是,代理对象生成了,那切面是如何织入的?

我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下Spring AOP是如何织入切面的。

publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable {
       MethodInvocation invocation = null;
       Object oldProxy = null;
       boolean setProxyContext = false;
 
       TargetSource targetSource = this.advised.targetSource;
       Class targetClass = null;
       Object target = null;
 
       try {
           //eqauls()方法,具目标对象未实现此方法
           if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){
                return (equals(args[0])? Boolean.TRUE : Boolean.FALSE);
           }
 
           //hashCode()方法,具目标对象未实现此方法
           if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){
                return newInteger(hashCode());
           }
 
           //Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知
           if (!this.advised.opaque &&method.getDeclaringClass().isInterface()
                    &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                // Service invocations onProxyConfig with the proxy config...
                return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args);
           }
 
           Object retVal = null;
 
           if (this.advised.exposeProxy) {
                // Make invocation available ifnecessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
           }
 
           //获得目标对象的类
           target = targetSource.getTarget();
           if (target != null) {
                targetClass = target.getClass();
           }
 
           //获取可以应用到此方法上的Interceptor列表
           List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);
 
           //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
           if (chain.isEmpty()) {
                retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
           } else {
                //创建MethodInvocation
                invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
           }
 
           // Massage return value if necessary.
           if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy)
                    &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                // Special case: it returned"this" and the return type of the method
                // is type-compatible. Notethat we can't help if the target sets
                // a reference to itself inanother returned object.
                retVal = proxy;
           }
           return retVal;
       } finally {
           if (target != null && !targetSource.isStatic()) {
                // Must have come fromTargetSource.
               targetSource.releaseTarget(target);
           }
           if (setProxyContext) {
                // Restore old proxy.
                AopContext.setCurrentProxy(oldProxy);
           }
       }
    }

主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。

首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:

public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
                   MethodCacheKeycacheKey = new MethodCacheKey(method);
                   List<Object>cached = this.methodCache.get(cacheKey);
                   if(cached == null) {
                            cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                                               this,method, targetClass);
                            this.methodCache.put(cacheKey,cached);
                   }
                   returncached;
         }

可以看到实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。
下面来分析下这个方法的实现:

/**
    * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor,
    * 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断
    * 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回.
    */
    publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {
       // This is somewhat tricky... we have to process introductions first,
       // but we need to preserve order in the ultimate list.
       List interceptorList = new ArrayList(config.getAdvisors().length);
 
       //查看是否包含IntroductionAdvisor
       boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);
 
       //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor
       AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
 
       Advisor[] advisors = config.getAdvisors();
        for (int i = 0; i <advisors.length; i++) {
           Advisor advisor = advisors[i];
           if (advisor instanceof PointcutAdvisor) {
                // Add it conditionally.
                PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;
                if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                    //TODO: 这个地方这两个方法的位置可以互换下
                    //将Advisor转化成Interceptor
                    MethodInterceptor[]interceptors = registry.getInterceptors(advisor);
 
                    //检查当前advisor的pointcut是否可以匹配当前方法
                    MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();
 
                    if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {
                        if(mm.isRuntime()) {
                            // Creating a newobject instance in the getInterceptors() method
                            // isn't a problemas we normally cache created chains.
                            for (intj = 0; j < interceptors.length; j++) {
                               interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));
                            }
                        } else {
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }
           } else if (advisor instanceof IntroductionAdvisor){
                IntroductionAdvisor ia =(IntroductionAdvisor) advisor;
                if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
                    Interceptor[] interceptors= registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
           } else {
                Interceptor[] interceptors =registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
           }
       }
       return interceptorList;
}

这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor.
接下来我们再看下得到的拦截器链是怎么起作用的。

if (chain.isEmpty()) {
                retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
            } else {
                //创建MethodInvocation
                invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
            }

从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码

public Object proceed() throws Throwable {
       //  We start with an index of -1and increment early.
       if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {
           //如果Interceptor执行完了,则执行joinPoint
           return invokeJoinpoint();
       }
 
       Object interceptorOrInterceptionAdvice =
           this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
       
       //如果要动态匹配joinPoint
       if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){
           // Evaluate dynamic method matcher here: static part will already have
           // been evaluated and found to match.
           InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
           //动态匹配:运行时参数是否满足匹配条件
           if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {
                //执行当前Intercetpor
                returndm.interceptor.invoke(this);
           }
           else {
                //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
                return proceed();
           }
       }
       else {
           // It's an interceptor, so we just invoke it: The pointcutwill have
           // been evaluated statically before this object was constructed.
           //执行当前Intercetpor
           return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
       }
}

SpringMVC工作流程图
一图识百文(SpringMVC流程图)
① 客户端的所有请求都交给前端控制器DispatcherServlet来处理,它会负责调用系统的其他模块来真正处理用户的请求。
② DispatcherServlet收到请求后,将根据请求的信息(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)以及HandlerMapping的配置找到处理该请求的Handler(任何一个对象都可以作为请求的Handler)。
③在这个地方Spring会通过HandlerAdapter对该处理器进行封装。
④ HandlerAdapter是一个适配器,它用统一的接口对各种Handler中的方法进行调用。
⑤ Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServlet,ModelAndView顾名思义,包含了数据模型以及相应的视图的信息。
⑥ ModelAndView的视图是逻辑视图,DispatcherServlet还要借助ViewResolver完成从逻辑视图到真实视图对象的解析工作。
⑦ 当得到真正的视图对象后,DispatcherServlet会利用视图对象对模型数据进行渲染。
⑧ 客户端得到响应,可能是一个普通的HTML页面,也可以是XML或JSON字符串,还可以是一张图片或者一个PDF文件。

·

参考地址:
https://www.cnblogs.com/xdp-gacl/p/4249939.html
https://baijiahao.baidu.com/s?id=1613047743708688271&wfr=spider&for=pc
https://blog.csdn.net/moreevan/article/details/11977115/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值