Posts tagged Spring

MVC容器初始化

0

MVC中的ApplicationContext初始化

这一部分,我们主要关注Spring IoC容器是如何在MVC中工作的,先看一张图来了解下Spring MVC的ApplicationContext的层次结构:

从这幅图片中我们可以看到,一个DispatcherServlet有一个他自己的WebApplicationContext,这个WebApplicationContext包含了诸如控制器,视图解析器等东西,并且继承了在根WebApplicationContext中定义的所有的Bean。而这个根WebApplicationContext包含了中间层的Service Bean,数据源相关Bean等Bean。

Root WebApplicationContext初始化

在Spring里面,一些Service对象,业务相关的对象,以及数据访问对象等,都是放在一个独立的业务上下文(Business Context)中的,因为这些Bean大多是在多个Servlet之间共享的,所以这些Bean都是被包含在Root WebApplicationContext中的,Root WebApplicationContext的初始化入口可以看下Spring Web项目中web.xml的配置,里面配置了一个ContextLoaderListener,从名字上看就是上下文载入器的监听器,而Root WebApplicationContext的初始化入口就在这里,从Listener的接口规范我们知道,listener的初始化方法是contextInitialized(ServletContextEvent event)方法,我们就来看下这个方法的实现:

public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = createContextLoader();
    if (this.contextLoader == null) {
        this.contextLoader = this;
    }
	this.contextLoader.initWebApplicationContext(event.getServletContext());
}

这个方法首先创建了一个contextLoader,然后调用了它的initWebApplicationContext方法,看下这个方法的实现:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // 检查是否已经存在一个Root WebApplicationContext了,如果存在了,则不创建,直接抛出一个异常
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // 获取Root WebApplicationContext的父Context
        ApplicationContext parent = loadParentContext(servletContext);

        // 创建WebApplicationContext
        this.context = createWebApplicationContext(servletContext, parent);
        // 将此Context作为Root WebApplicationContext加入到servletContext中去
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
        }

        return this.context;
    }
    catch (RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
    catch (Error err) {
        logger.error("Context initialization failed", err);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
        throw err;
    }
}

我们可以看到这个方法里面调用了一个createWebApplicationContext方法,看下这个方法的实现:

protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
    // 确定上下文的Class
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 创建一个WebApplicationContext
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    // Assign the best possible id value.
    if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
        // Servlet <= 2.4: resort to name specified in web.xml, if any.
        String servletContextName = sc.getServletContextName();
        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                ObjectUtils.getDisplayString(servletContextName));
    }
    else {
        // Servlet 2.5's getContextPath available!
        try {
            String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc);
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(contextPath));
        }
        catch (Exception ex) {
            throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex);
        }
    }

    wac.setParent(parent);
    wac.setServletContext(sc);
    wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
    customizeContext(sc, wac);
    // 又看到了我们熟悉的refresh()方法,这个方法就是初始化Root容器的地方
    wac.refresh();
    return wac;
}

DispatcherServlet对应的WebApplicationContext初始化

既然有Root WebApplicationContext,就会有相应的Sub WebApplication Context,一个在web.xml中定义的DispatcherServlet一般上都是对应一个WebApplicationContext,里面包含了一些诸如Spring Controller的一些东西,对着这个WebApplication的启动过程,可以看下DispatcherServlet,DispatcherServlet继承了FrameworkServlet,这个类实现了Servlet接口,从Servlet接口的定义知道,Servlet的初始化是调用init方法来初始化的,而FrameworkServlet的父类HttpServletBean在init方法里面调用了一个initServletBean方法,我们就来看下FrameworkServlet对initServletBean方法的实现:

protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // 初始化WebApplicationContext
        this.webApplicationContext = initWebApplicationContext();
        // 初始化FrameworkServlet,默认实现为空
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isInfoEnabled()) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                elapsedTime + " ms");
    }
}

上述这段代码中的关键部分就是initWebApplicationContext方法,看下这个方法里面干了什么事情:

protected WebApplicationContext initWebApplicationContext() {
    // 根据attrName获取WebApplicationContext,防止重复初始化
    WebApplicationContext wac = findWebApplicationContext();
    if (wac == null) {
        // 获取Root WebApplicationContext
        WebApplicationContext parent =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        // 根据获取的到Root WebApplicationContext创建出一个Sub WebApplicationContext
        wac = createWebApplicationContext(parent);
    }

    if (!this.refreshEventReceived) {
        // Apparently not a ConfigurableApplicationContext with refresh support:
        // triggering initial onRefresh manually here.
        onRefresh(wac);
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}

这段代码使用了Root WebApplicationContext创建了一个Sub WebApplicationContext,创建的过程调用了FrameworkServlet的createWebApplicationContext方法,这个方法和前面提到的createWebApplicationContext方法类似,但是稍有不同,来看下其实现:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
    	this.logger.debug("Servlet with name '" + getServletName() +
    			"' will try to create custom WebApplicationContext context of class '" +
    			contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    	throw new ApplicationContextException(
    			"Fatal initialization error in servlet with name '" + getServletName() +
    			"': custom WebApplicationContext class [" + contextClass.getName() +
    			"] is not of type ConfigurableWebApplicationContext");
    }
    ConfigurableWebApplicationContext wac =
    		(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    // Assign the best possible id value.
    ServletContext sc = getServletContext();
    if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
    	// Servlet <= 2.4: resort to name specified in web.xml, if any.
    	String servletContextName = sc.getServletContextName();
    	if (servletContextName != null) {
    		wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
    				"." + getServletName());
    	}
    	else {
    		wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
    	}
    }
    else {
    	// Servlet 2.5's getContextPath available!
    	wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath() +
    			"/" + getServletName());
    }

    wac.setParent(parent);
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.setConfigLocation(getContextConfigLocation());
    // 注意这里注册了一个监听器,后面在完成refresh()以后分发事件就会触发这个监听器
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    postProcessWebApplicationContext(wac);
    // 调用refresh()方法
    wac.refresh();

    return wac;
}

这段代码中需要注意的一点是FrameworkContext向WebApplicationContext注册了一个监听器,会在WebApplicationContext完成refresh操作的时候调用DispatcherServlet的onRefresh方法,它又调用了initStrategies方法,看下initStrategies方法的具体实现:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
}

在这个方法里面Spring将所有的相关组件都初始化好.

感想

不论是WebX还是Spring MVC都是基于Servlet而做的web开发框架,其入口都是和普通的servlet应用是一样的,所以只需要抓住web.xml中定义的Listener,Servlet等就能够把握住学习的入口。

Spring MVC概览

0

Spring MVC概览


这是一张从Spring Reference上搞过来的图,展示了Spring MVC框架对于请求的处理过程,其中我们可以看到在Spring MVC中,使用了在web请求处理中非常流行的前端控制器模式,这个前端控制器负责将请求的处理代理给相应的Controller,并且负责将页面的渲染代理给相应的视图组件,可以说前端控制器在这里起到了一个请求的控制调度中心的作用。

Spring MVC中的主要组件

在Spring MVC中,有很多的组件分别负责处理不同的事情,我们可以先了解下这些组件到底是处理什么事情的,然后再去了解几个主要的组件是如何工作的:

  • Controller:就是MVC中的C部分
  • Handler Mapping:处理符合映射规则的pre-proccessors,post-processors和controllers
  • View Resolver:解析视图名称
  • Locale Resolver:查看本地信息,用于i18n,几个默认的实现包括AcceptHeaderLocaleResolver,CookieLocaleResolver,SessionLocaleResolver,用户可以通过配置LocaleChangeInterceptor来对Local信息做出修改。
  • Theme Resolver:解析应用中所使用的主题
  • Multipart File Resolver:处理上传的文件
  • Handler Exception Resolvers:处理异常

Spring AOP之横切逻辑的执行

0

AOP横切逻辑的执行

AOP横切逻辑的执行主要由一个接口来执行AopProxy,它有两个实现JdkDynamicAopProxy和Cglib2AopProxy,Spring会根据不同的情况选择不同的AopProxy来执行横切逻辑,先来看一下JdkDynamicAopProxy的一个情况:

JdkDynamicAopProxy

看下JdkDynamicAopProxy的Invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Class targetClass = null;
    Object target = null;

    try {
        // equals hashcode等方法处理
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;

        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // May be null. Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        // Get the interception chain for this method.
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // 检查拦截器链是否为空,如果为空,就直接通过反射调用目标对象的方法,避免创建多余的MethodInvocation对象
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        }
        else {
            // 创建一个MethodInvocation
            invocation = new ReflectiveMethodInvocation(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. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

以上这个方法的重点是:invocation.proceed()方法,这个方法是通过递归的方式来调用拦截器链里面的每一个拦截器,我们看一下这个方法的具体实现:

public Object proceed() throws Throwable {
    // 从-1开始,然后先加
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        // 如果上一次调用的已经是最后一个拦截器,则调用切面逻辑
        return invokeJoinpoint();
    }

    Object interceptorOrInterceptionAdvice =
        this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // 动态方法匹配
        InterceptorAndDynamicMethodMatcher dm =
        (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // 动态方法匹配失败,然后直接调用拦截器链的下一个拦截器
            // Skip this interceptor and invoke the next in the chain.
            return proceed();
        }
    }
    else {
        // 非动态方法匹配,静态方法匹配,之前已经匹配过,这是一个拦截器,直接调用
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

单看这一段代码还看不出递归影子,看下MethodInterceptor的各个实现类的invoke方法便可以看到,虽然MethodInterceptor的实现类对于invoke方法的具体实现有所不同,然是它们都调用了mi.proceed()这个方法,从而实现对拦截器链的递归调用。

Cglib2AopProxy

Cglib调用横切逻辑的方式和Jdk基本相似,代码上区别在于调用的方法不一样那个,Cglib2AopProxy调用的是其内部类DynamicAdvicedInterceptor中的intercept方法:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Class targetClass = null;
    Object target = null;
    try {
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // May be <code>null</code>. Get as late as possible to minimize the time we
        // "own" the target, in case it comes from a pool.
        target = getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // 检查是否只是有一个InvokerInterceptor:没有真正的Advice,直接反射调用目标方法
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // 跳过创建MethodInvocation的步骤:直接调用目标方法
            // 注意最后一个invoker必须使一个InvokerInterceptor,这样我们才能知道它并没有做任何事情,只是对目标做了一个反射操作
            retVal = methodProxy.invoke(target, args);
        }
        else {
            // 创建一个MethodInvocation并调用
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = massageReturnTypeIfNecessary(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if (target != null) {
            releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

从这段代码中可以看出,Cglib2AopProxy是创建了一个CglibMethodInvocation并调用其proceed()方法,而CglibMethodInvocation是继承了RefectiveMethodInvocation,所以最后还是调用了ReflectiveMethodInvocation的proceed()方法。但是CglibMethodInvocation重写了ReflectiveMethodInvocation的invokeJoinpoint()方法,按照作者写的注释似乎能带来微小的性能提升:

/**
 * Gives a marginal performance improvement versus using reflection to
 * invoke the target when invoking public methods.
 */
protected Object invokeJoinpoint() throws Throwable {
    if (this.protectedMethod) {
        return super.invokeJoinpoint();
    }
    else {
        return this.methodProxy.invoke(this.target, this.arguments);
    }
}

Spring AOP之代理对象的生成

0

AOP代理对象的生成

在声明式的AOP中,我们定义了一个ProxyFactoryBean来生成代理对象,在Spring配置文件中的配置如下:

<bean id="beanA" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="beanAImpl" />
    <property name="interceptorNames" value="testAdvisor" />
    <property name="proxyInterfaces" value="com.alibaba.khotyn.test.BeanInterface">
</property>

而ProxyFactoryBean之所以叫做ProxyFactoryBean是因为它继承了FactoryBean,所以要知道关于AOP的代理对象如何生成,只需要关注ProxyFactoryBean的getObeject()方法即可,我们先来看一下它的实现:

public Object getObject() throws BeansException {
    // 初始化通知器链
    initializeAdvisorChain();
    if (isSingleton()) {
        // 获取单例对象
        return getSingletonInstance();
    }
    else {
        if (this.targetName == null) {
            logger.warn("Using non-singleton proxies with singleton targets is often undesirable." +
                            "Enable prototype proxies by setting the 'targetName' property.");
        }
        // 获取Prototype对象
        return newPrototypeInstance();
    }
}

这里面两个关键的方法是getSingletonInstance()和newPrototypeInstance(),看下这两个方法的实现:

private synchronized Object getSingletonInstance() {
    if (this.singletonInstance == null) {
        this.targetSource = freshTargetSource();
        if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
            // Rely on AOP infrastructure to tell us what interfaces to proxy.
            setInterfaces(ClassUtils.getAllInterfacesForClass(this.targetSource.getTargetClass()));
        }
        // Eagerly initialize the shared singleton instance.
        super.setFrozen(this.freezeProxy);
        // 创建一个AopProxy,然后通过它获取到代理对象
        this.singletonInstance = getProxy(createAopProxy());
        // We must listen to superclass advice change events to recache the singleton
        // instance if necessary.
        addListener(this);
    }
    return this.singletonInstance;
}

从这段代码中看出,Spring是首先创建一个AopProxy对象(JdkDynamicAopProxy或者是Cglib2AopProxy),然后通过它来创建出真正的代理对象(JdkDynamicAopProxy,Cglib2AopProxy分别根据自己的方法来创建对象),而newPrototypeInstance()方法的实现和getSingletonInstance()的实现类似,这里不做赘述。

Spring AOP概览

0

AOP概述

介绍AOP的基本概念,首先对AOP做一个大致的了解

AOP原理以及关键概念


下面结合上面的AOP原理图来看一下AOP的一些关键概念

  • JoinPoint:代表程序运行时的一个具体的织入点
  • Pointcut:一组JoinPoint的集合,通常由正则表达式来定义
  • Advice:切面的逻辑
  • Advisor:一个Advisor包含一个Advise和一个PointCut

Spring AOP实现的基本类图

了解了这些基本的概念以后,来看下Spring使如何实现这些概念的,看下面的类图

下面对几个重要的类做简要说明:

  • Advice:Advice接口,使一个标签接口,没有定义任何成员和方法
  • BeforeAdvice,AfterAdvice,ThrowsAdvice:这三个接口都只是标签接口,没有定义任何成员方法
  • MethodBeforeAdvice:接口,定义了方法调用前调用的Advice
  • AfterReturningAdvice:接口,定义了方法调用返回后的Advice
  • Pointcut:Spring Pointcut的核心抽象,一个Pointcut包含了一个ClassFilter和一个MethodMatcher
  • MethodMatcher:Pointcut的一部分,检查目标方法是否和advice是相匹配的
  • Advisor:Advisor接口,持有一个Advice
  • PointcutAdvisor:在Advisor接口的基础上持有了一个Pointcut

从整个类图我们也可以看出来Advice,Pointcut以及Advisor之间的关系

Go to Top