@nextleaf
2018-10-15T01:05:46.000000Z
字数 18496
阅读 1148
Spring
AOP
代理
MVC
AOP(Aspect Oriented Programming),即面向方面(切面)编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。
(日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。)
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect
",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
类是对物体特征的抽象,切面就是对横切关注点的抽象。
通知和切点是切面的最基本的元素,切面是通知和切点的结合
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
对连接点进行拦截的定义
切点和连接点之间的关系是: 切点的定义会匹配通知的一个或多个连接点. 也就是切点定义了通知被应用的位置, 即连接点
所谓通知指的就是指拦截到连接点之后要执行的代码,就是切面的功能, 就是切面要完成的工作. 通知定义了切面是什么以及在什么时候使用通知分为前置、后置、异常、最终、环绕通知五类
代理的目标对象(由execution或within指定?)
把切面应用到目标对象中,并且创建新的代理对象的过程;Spring使用的是运行时织入,AOP代理类在切点动态地织入了增强处理。
定义:引入允许我们通过切面向现有的Spring Bean添加新方法或者新属性
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
【我们通过切面给对象中的一个方法加上了它原本没有的功能
Spring的切面由包裹了目标对象的代理实现. 代理类处理方法的调用, 执行额外的切面逻辑, 并调用目标方法. 而现在对于引入而言, 除了可以执行目标对象已有的方法(已有的接口), 还可以执行新的方法(也就是目标对象没有的方法), 即新的接口, 即底层实现类(目标对象)并没有实现的方法(接口).
我们需要注意的是, 当引入接口的方法被调用的时候, 代理会把此调用委托给实现了新接口的某个其它对象. 也就是说, 实际上是将一个Bean的实现拆分到了多个类中】
两个流行的AOP框架:
Spring AOP和AspectJ(AspectJ会在编译阶段生成AOP代理类,即AspectJ是静态代理的增强)。
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的注解功能,注意只是使用了它的注解功能而已,并不是核心功能
AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。
所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法(像环绕通知的.proceed())。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口没有声明接口,则Spring将自动使用CGLIB动态代理)。JDK动态代理的核心是InvocationHandler接口和Proxy类。
JDK动态代理伪代码:
public class CostDao_proxy implements ICostDao {
//用于接收注入的被代理对象
private ICostDao src;
public CostDao_proxy(ICostDao src) {
this.src = src;
}
public void save() {
//调用被代理对象的方法
src.save();
//调用方面组件逻辑......
}
}
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类(即使proxy-target-class设置为false)。
CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
CGLIB动态代理伪代码:
public class CostDao_proxy extends CostDaoImpl {
public void save() {
super.save();
//调用方面组件的业务逻辑
}
}
题外:
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)之上
通过xml文件来进行,大概有四种方式:
<aop:config>
来配置<aop: aspectj-autoproxy>
来配置,使用AspectJ的注解来标识通知及切入点啊
通常使用第3种或第4种方式来配置Spring AOP。
1 . 可能需要手动导入的依赖(jar包)
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
2 . AOP的使用步骤
(1) 创建方面组件
(2) 在applicationContext.xml中声明方面组件
(3) 引用方面组件
<aop:config>
<!--声明方面,引用方面组件-->
<aop:aspect ref="方面组件id">
<!--指定方面组件被谁引用,即声明切入点和通知类型-->
<aop:after method="方面组件的方法名" pointcut="表达式"/>
</aop:aspect>
</aop:config>
例子:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--class可替换为其他技术的实现类,如JDBC,Hibernate-->
<bean id="costDao" class="com.nl.springpracticaltraining.dao.CostDaoImpl" scope="singleton" lazy-init="default"
init-method="myinit" destroy-method="mydestroy">
<!--<property name="dataSource" ref="ds"/>-->
</bean>
<!--Action组件使用prototype方式创建对象-->
<bean id="addCostAction" class="com.nl.springpracticaltraining.action.AddCostAction" scope="prototype">
<!--使用接口属性来封装,使用setter方式注入,ref依赖的组件ID-->
<property name="iCostDao" ref="costDao"/>
</bean>
<bean id="updateCostAction" class="com.nl.springpracticaltraining.action.UpdateCostAction" scope="prototype">
<!--使用接口属性来封装,使用构造方式注入,index,构造方法的参数位置,ref=依赖的组件ID-->
<constructor-arg index="0" ref="costDao"/>
</bean>
<!--
Spring AOP
通知执行顺序为: 前置通知→环绕通知→正常返回通知/异常返回通知→返回通知
-->
<!--方面(切面)组件声明-->
<bean id="loggerBean" class="com.nl.springpracticaltraining.aspect.LoggerBean"/>
<bean id="exceptionBean" class="com.nl.springpracticaltraining.aspect.ExceptionBean"/>
<aop:config>
<!--引用方面(切面)组件,order数字越小,优先级越高(可选)-->
<aop:aspect ref="loggerBean" order="1">
<!--指定方面(切面)组件被谁(pointcut表达式指定)引用,即声明通知类型和切入点(定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作)-->
<aop:after method="mylogforAction" pointcut="within(com.nl.springpracticaltraining.action.*)"/>
<!--AspectJ语法:https://blog.csdn.net/sunlihuo/article/details/52701548-->
<aop:after method="mylogforDao" pointcut="within(com.nl.springpracticaltraining.dao.*)"/>
<aop:around method="mylogforAround" pointcut="within(com.nl.springpracticaltraining.action.*)"/>
</aop:aspect>
<!--异常方面组件-->
<aop:aspect ref="exceptionBean">
<aop:after-throwing method="aVoid" throwing="e" pointcut="within(com.nl.springpracticaltraining.action.*)"/>
</aop:aspect>
</aop:config>
</beans>
下午
可能需要手动导入的依赖(jar包)
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
(1) 开启组件扫描
<context:component-scan base-package="com.nl.springpracticaltraining"/>
(2) 开启AOP的注解配置
<!--高版本Spring自动选择代理模式,无需配置proxy-target-class=""-->
<aop:aspectj-autoproxy />
例如:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--组件扫描-->
<context:component-scan base-package="com.nl.springpracticaltraining"/>
<!--
开启AOP的AspectJ注解自动代理(也可以通过JavaConfig启用自动代理)
高版本Spring自动选择代理模式,无需配置proxy-target-class=""
proxy-target-class表示是否代理目标类,默认是false,也就是代理接口(JDK动态代理)即使proxy-target-class设置为false,如果目标类没有声明接口,则Spring将自动使用CGLIB动态代理
proxy-target-class为true则是基于类的代理将起作用(需要cglib库)
-->
<!---->
<aop:aspectj-autoproxy />
</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")
待补充
待补充
真实角色与代理类同时实现一个接口,真实角色只处理与它核心业务相关的,其他的全部由代理角色完成。
(编译阶段生成代理类?)
/**
* 抽象接口
* @author sg
*/
public interface Subject {
void printBeforeLogging();
void operation();
void printAfterLogging();
}
//真实角色
public class RelSubject implements Subject{
@Override
public void printBeforeLogging() {
System.out.println("RelSubject printBeforeLogging");
}
@Override
public void operation() {
System.out.println("RelSubject operation");
}
@Override
public void printAfterLogging() {
System.out.println("RelSubject printAfterLogging");
}
}
//代理角色
public class Proxy implements Subject{
private Subject subject;
public Proxy(Subject subject) {
super();
this.subject = subject;
}
@Override
public void printBeforeLogging() {
System.out.println("Proxy printBeforeLogging");
}
@Override
public void operation() {
//真实的角色来完成
subject.operation();
}
@Override
public void printAfterLogging() {
System.out.println("Proxy printAfterLogging");
}
}
//客户端代码
public class Client {
public static void main(String[] args) {
Subject subject=new RelSubject();
Subject proxy=new Proxy(subject);
proxy.printBeforeLogging();
proxy.operation();
proxy.printAfterLogging();
}
}
public interface Calculate {
void operate();
}
public class CalculateImpl implements Calculate{
@Override
public void operate() {
System.out.println("Operating");
}
}
//代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler{
//目标对象
private Object target;
public DynamicProxy(Object target){
this.target= target;
}
public Object getProxy(){
//调用Proxy的静态方法
return
Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
//客户端
public class Client {
public static void main(String[] args) {
Calculate target=new CalculateImpl();
Calculate proxy = (Calculate)new DynamicProxy(target).getProxy();
proxy.operate();
}
}
首先下载所必须Jar包:cglib
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
public CglibProxyFactory(Object target) {
super();
this.target = target;
}
public Object myCglibLogging() {
Enhancer enhancer = new Enhancer();
// 生成得代理类对象为该类的子类
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 调用动态方法的时候会调用此方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object result = method.invoke(target, args);
System.out.println(method);
return result;
}
}
//测试
public class CglibProxyTest {
public static void main(String[] args) {
CglibProxyTest ct=new CglibProxyTest();
CglibProxyTest myCglibLogging = (CglibProxyTest) new CglibProxyFactory(ct).myCglibLogging();
myCglibLogging.run();
}
public void run(){
System.out.println("running");
}
}
cglib基于接口的代理方式的缺点是所要代理的应该实现接口,而基于cglib的代理如果定义为final也是无法实现的了。
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对用户进行响应
1、springmvc基于方法开发的,struts2基于类开发的。springmvc将url和controller里的方法映射。映射成功后springmvc生成一个Handler对象,对象中只包括了一个method。方法执行结束,形参数据销毁。springmvc的controller开发类似web service开发。
2、springmvc可以进行单例开发,并且建议使用单例开发,struts2通过类的成员变量接收参数,无法使用单例,只能使用多例。
3、经过实际测试,struts2速度慢,在于使用struts标签,如果使用struts建议使用jstl。
在Spring MVC框架中, 其中的3个组件是用户必须要定义和扩展的:
定义URL映射规则,
实现业务逻辑的Handler实例对象(Controller),
渲染模板资源(View)
Spring容器的创建是在FrameworkServlet的initServletBean()方法中完成的, 这个方法会创建WebApplicationContext对象, 并调用其refresh()方法来完成配置文件的加载, 配置文件的加载是先查找Servlet的init-param参数中设置的路径, 如果没有, 会根据namespace+Servlet的名称来查找XML文件. Spring容器在加载时会调用DispatcherServlet的initStrategies()方法来完成DispatcherServlet中定义的初始化工作, 也就是初始化Spring MVC的框架需要的8个组件, 这8个组件对应的8个Bean对象都保存在DispatcherServlet类中.
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:
<bean id="demo" name="/demo" class="WebXu.Demo">
<property name="viewPage" value="/demo.html"/>
</bean>
<!--通过alias标签来为Bean提供多个名称, 而这些所有的名称都指向同一个Bean. -->
<!--为Bean增加别名, 时为了让应用的每一个组件能更容易的对公共组件进行引用.-->
<alias name="/demo" alias="/demo1"/>
<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的处理逻辑的关键就是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的大致调用流程如下:
- 整个Spring MVC的调用是从DispatcherServlet的doervice()方法开始的, 在doService()方法中会将ApplicationContext/localeResolver/themeResolver等对象添加到request中以便于后面使用.
- 接着就是调用doDispatch()方法, 这个方法就用来处理用户请求.
- 调用checkMutipart()方法(mutipart是一种数据格式), 这个方法会检查当前的请求是否为post请求, contentType是否以mutipart/为前缀, 如果是, 将request包装成MutipartHttpRequest对象.
- 执行getHandler()方法.
- new HandlerExecutionChain(): 在handlerMappings集合中依次匹配每个HandlerMapping的getHandler(request)方法直到某个HandlerMapping的URL匹配成功, 并返回HandlerExecutionChain对象.
- getIntercepters():获取该Handler中定义的所有Intercepter对象.
- 执行HandlerIntercepter的preHandler()方法, 如果返回false, 当前请求在执行afterHandler()之后将立即返回.
- 执行getHandlerAdapter()方法.
- 得到一个HandlerAdapter对象ha, 调用ha.supports()方法, 该方法会返回在HandlerAdapters集合中第一个支持该Handler的HandlerAdapter对象
在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)中
对于Spring MVC的View模块来说, 它由两个组件支持: RequestToViewNameTranslator和ViewResolver.
View的大致调用流程如下:
- 最先调用DispatcherServlet类的getDefaultViewName()方法, 如果Handler中返回的ModelAndView对象的ViewName没有设置, 那么就会调用viewNameTranslator获取的ViewName.
- 调用RequestToViewNameTranslator的getViewName()方法.
- 调用LocaleResolver接口的resolverLocale()方法.
- 调用ViewResolver接口的resolverViewName方法, 返回View对象, 然后调用createView()方法, 将ViewClass属性对应的InternalResolverView实例化.
- 最后调用InternalResolverView的render()方法渲染出JSP页面.
一些面试题
spring mvc面试题
spring mvc面试题2
参考资料之一:
Spring AOP原理分析一次看懂(2017年08月10日)