中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Spring源碼探究:容器

2018-12-21    來源:importnew

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬Linux鏡像隨意使用

結(jié)合源碼分析 Spring 容器與 SpringMVC 容器之間的關(guān)系


問題

問題描述:項(xiàng)目中發(fā)現(xiàn),自定義切面注解在 Controller 層正常工作,在 Service 層卻無法正常工作。為了便于分析,去掉代碼中的業(yè)務(wù)邏輯,只留下場(chǎng)景。

自定義注解,打印時(shí)間

/**
 * Description: 自定義打印時(shí)間的注解
 * Created by jiangwang3 on 2018/5/9.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface PrintTime {
}

注解解析器

/**
 *Description:打印時(shí)間注解的解析器
 * @author jiangwang
 * @date 11:28 2018/5/14
 */
@Aspect
public class PrintTimeProcessor {
    private Logger LOGGER = LoggerFactory.getLogger(getClass());
    @Pointcut("@annotation(com.foo.service.annotation.PrintTime)")
    public void printTimePoint() {
    }
    @Around("printTimePoint()")
    public Object process(ProceedingJoinPoint jp) throws Throwable{
        System.out.println();
        LOGGER.error("開始運(yùn)行程序。。。Start==>");
        Object proceed = jp.proceed();
        LOGGER.error("結(jié)束啦,運(yùn)行結(jié)束==>");
        System.out.println();
        return proceed;
    }
}

Controller層

/**
 * @author jiangwang
 * @date  2018/5/14
 */
@RestController
@RequestMapping(value = "/user")
public class UserController {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Resource
    private UserService userService;
    @RequestMapping(value = "/serviceAspect", method={RequestMethod.GET})
    public  String serviceAspect(){
        return userService.serviceAspect();
    }
    @RequestMapping(value = "/controllerAspect", method={RequestMethod.GET})
    @PrintTime
    public  String name(){
        logger.info("Controller層----測(cè)試切面");
        return "controllerAspect";
    }
}

Service層

/**
 * @author jiangwang
 * @date 11:34 2018/5/14
 */
@Service
public class UserService {
    private Logger logger = LoggerFactory.getLogger(getClass())
    @PrintTime
    public String serviceAspect(){
        logger.info("Service層---測(cè)試切面");
        return "serviceAspect";
    }
}

spring.xml 配置文件,主要部分

<context:annotation-config />
<!-- 動(dòng)態(tài)代理開啟 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<context:component-scan base-package="com.foo" >
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 公共配置引入 -->
<import resource="classpath:spring/spring-config-dao.xml" />

springmvc.xml 配置文件,主要部分

<mvc:annotation-driven />
<mvc:default-servlet-handler />
<!-- 動(dòng)態(tài)代理開啟 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- mvc controller -->
<context:component-scan base-package="com.foo.web.controller" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
</context:component-scan>
<bean class="com.foo.service.processor.PrintTimeProcessor"/>

以上為主要代碼。項(xiàng)目運(yùn)行之后,發(fā)現(xiàn)在 Service 層的注解切面未生效,而在 Controller 層正常。而當(dāng)我將 springmvc.xml 中的

<bean class="com.foo.service.processor.PrintTimeProcessor"/>

遷移至 spring.xml 中,發(fā)現(xiàn) Service 層與 Controller 層的注解切面均可正常運(yùn)行。WHY???


從源碼的角度探究該問題

由于源碼中的方法較長(zhǎng),所以只貼出重點(diǎn)且與主題相關(guān)的代碼。建議結(jié)合本地源碼一起看。

為了說清楚這個(gè)問題,咱們先看一下Spring容器是如何實(shí)現(xiàn) Bean 自動(dòng)注入(簡(jiǎn)化版)

Web 項(xiàng)目的入口是 web.xml,所以咱們從它開始。

web.xml 配置文件,主要部分

<!-- Spring Config -->
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring/spring-config.xml</param-value>
</context-param>

<!-- SpringMvc Config -->
<servlet>
  <servlet-name>springMvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/spring-mvc.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>springMvc</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

Spring 容器 Bean 加載流程

從 Spring 配置部分可以看出,ContextLoaderListener 監(jiān)聽器是 Spring 容器的入口,進(jìn)入該文件:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

ContextLoaderListener 監(jiān)聽器一共有四個(gè)方法,可以很容易地判斷出來,進(jìn)入該監(jiān)聽器后,會(huì)進(jìn)入初始化方法:contextInitialized。繼而進(jìn)入?initWebApplicationContext?方法,方法注釋中?“Initialize Spring’s web application context for the given servlet context”,明確表明了該方法的目的是初始化 Spring Web 應(yīng)用。這段代碼中有兩句話比較關(guān)鍵:

this.context = createWebApplicationContext(servletContext);

創(chuàng)建 Web 應(yīng)用容器,即創(chuàng)建了 Spring 容器;

configureAndRefreshWebApplicationContext(cwac, servletContext);

配置并刷新Spring容器。后續(xù)發(fā)生的所有事,都是從它開始的。進(jìn)入,里面的重點(diǎn)代碼是:

wac.refresh();

refresh()?方法是spring容器注入bean的核心方法,每一行代碼都很重要。代碼結(jié)構(gòu)也非常優(yōu)美,每一行代碼背后都完成了一件事,代碼結(jié)構(gòu)比較容易理解。由于內(nèi)容較多,只講里面跟主題相關(guān)的兩句話:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

獲取 Bean 工廠,把你配置文件中的內(nèi)容,放在 Bean 工廠中,留著后面創(chuàng)建 Bean 時(shí)用。

finishBeanFactoryInitialization(beanFactory);

開始創(chuàng)建 Bean,即實(shí)現(xiàn) Spring 中的自動(dòng)注入功能。進(jìn)入該方法后,末尾有這么一句話:

beanFactory.preInstantiateSingletons();

繼續(xù)跟進(jìn),貼出該方法中的重點(diǎn)代碼:

getBean(beanName);

我們?cè)?preInstantiateSingletons() 方法中,會(huì)發(fā)現(xiàn)有多個(gè)地方出現(xiàn)了 getBean() 方法,究竟咱們貼出來的是哪一句?無關(guān)緊要。跟進(jìn)去之后,

@Override
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}

這里調(diào)用了 doGetBean() 方法,Spring 中只要以?do?命名的方法,都是真正干活的。重點(diǎn)代碼分段貼出分析:

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
    if (logger.isDebugEnabled()) {
        if (isSingletonCurrentlyInCreation(beanName)) {
            logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                    "' that is not fully initialized yet - a consequence of a circular reference");
        }
        else {
            logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
        }
    }
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

直接獲取單例 Bean,若沒有取到,繼續(xù)往下走:

// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
    // Not found -> check parent.
    String nameToLookup = originalBeanName(name);
    if (args != null) {
        // Delegation to parent with explicit args.
        return (T) parentBeanFactory.getBean(nameToLookup, args);
    }
    else {
        // No args -> delegate to standard getBean method.
        return parentBeanFactory.getBean(nameToLookup, requiredType);
    }
}

這一段代碼單獨(dú)看,不知所云,里面提到了一個(gè)詞:Parent。暫且跳過,后續(xù)會(huì)回來分析這一段。繼續(xù):

// Create bean instance.
if (mbd.isSingleton()) {
       sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
            @Override
             public Object getObject() throws BeansException {
                 try {
                     return createBean(beanName, mbd, args);
                  }
                  catch (BeansException ex) {
                      // Explicitly remove instance from singleton cache: It might have been put there
                      // eagerly by the creation process, to allow for circular reference resolution.
                      // Also remove any beans that received a temporary reference to the bean.
                      destroySingleton(beanName);
                      throw ex;
                 }
                }
         });
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

這段代碼中有 createBean,咱們的目的是分析 Bean 的創(chuàng)建過程,此處出現(xiàn)了?create,毫不猶豫地跟進(jìn),進(jìn)入實(shí)現(xiàn)類中的方法,有這么一句:

Object beanInstance = doCreateBean(beanName, mbdToUse, args);

剛才咱們提了,Spring 中有?do?命名的方法,是真正干活的。跟進(jìn):

instanceWrapper = createBeanInstance(beanName, mbd, args);

這句話是初始化 Bean,即創(chuàng)建了?Bean,等價(jià)于調(diào)用了一個(gè)類的空構(gòu)造方法。此時(shí),已經(jīng)成功地創(chuàng)建了對(duì)象,下文需要做的是,給該對(duì)象注入需要的屬性;

populateBean(beanName, mbd, instanceWrapper);

填充?Bean 屬性,就是剛才咱們提的,初始化一個(gè)對(duì)象后,只是一個(gè)空對(duì)象,需要給它填充屬性。跟進(jìn),看 Spring 是如何為對(duì)象注入屬性的,或者說,看一下 Spring 是如何實(shí)現(xiàn) Bean 屬性的自動(dòng)注入:

pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);

繼續(xù)進(jìn)入 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues 方法:

metadata.inject(bean, beanName, pvs);

這句話中,出現(xiàn)了?inject,這個(gè)詞的意思是“注入”。咱們可以斷定,Spring 的自動(dòng)注入,八成跟它有關(guān)了。進(jìn)入該方法:

<code>element.inject(target, beanName, pvs); </code>

與上一句一樣,只是做了一些參數(shù)處理,并沒有開始注入。繼續(xù)跟進(jìn)看:

Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));

看到這里,大概明白了 Spring 是如何自動(dòng)注入了。Java 反射相關(guān)的代碼,通過反射的方式給 field 賦值。這里的?field?是 Bean 中的某一個(gè)屬性,例如咱們開始時(shí)的 UserController 類中的?userService。getResourceToInject,獲取需要賦予的值了,其實(shí)這里會(huì)重新進(jìn)入 getBean 方法,獲取 Bean 值(例如 UserController 對(duì)象中需要注入 userService。),然后賦予 field。至此,Spring容器已經(jīng)初始化完成,Spring Bean注入的大概流程,咱們也已經(jīng)熟悉了;氐介_始初始化 Spring 容器的地方,ContextLoader 類 initWebApplicationContext 方法,

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

初始化 Spring 容器之后,將其放入了 servletContext 中。

咱們的問題是,“在項(xiàng)目中,自定義切面注解在 Controller 層正常工作,卻在 Service 層無法正常工作?”看完這個(gè),其實(shí)并沒有解答該問題,咱們下面繼續(xù)看 SpringMVC Bean的加載流程,看完 SpringMVC 后,答案會(huì)自動(dòng)浮出水面。


SpringMVC 容器 Bean 加載流程

同樣,從 web.xml 中的?SpringMVC 配置出發(fā),里面有 DispatcherServlet,這是 SpringMVC 的入口,跟進(jìn)之后發(fā)現(xiàn)方法較多,無法知道會(huì)執(zhí)行哪個(gè)方法。但是咱們要記住,DispatcherServlet?本質(zhì)上是一個(gè) Servlet,通過它的繼承關(guān)系圖也可以證明:

DispatcherServlet繼承關(guān)系圖

看一下 Servlet 的接口:

public interface Servlet {
    public void init(ServletConfig config) throws ServletException;
    public ServletConfig getServletConfig();
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;
    public String getServletInfo();
    public void destroy();
}

從 Servlet 接口方法中可以看出,Servlet 的入口是?init?方法,層層跟進(jìn)(一定要根據(jù) DispatcherServlet 繼承圖跟進(jìn)),進(jìn)入到了 FrameworkServlet 的?initServletBean()?方法,進(jìn)入方法,貼出重點(diǎn)代碼:

this.webApplicationContext = initWebApplicationContext();

字面理解,初始化?SpringMVC Web容器,進(jìn)入探究:

WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

前面咱們提到,Spring 容器初始化完成之后,放入了?servletContext?中。這里又從 servletContext 獲取到了?Spring 容器;

wac = createWebApplicationContext(rootContext);

字面理解創(chuàng)建 Web 應(yīng)用容器,且參數(shù)是 Spring 容器。跟進(jìn)方法:

ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

創(chuàng)建web應(yīng)用容器,即咱們所理解的 SpringMVC?容器在此創(chuàng)建了;

wac.setParent(parent);

這里是重點(diǎn),SpringMVC?容器將 Spring 容器設(shè)置成了自己的父容器

configureAndRefreshWebApplicationContext(wac);

這個(gè)方法剛才在分析 Spring Bean 加載流程時(shí),分析過了。其中有一段,前面說,“暫且跳過,后續(xù)會(huì)回來分析這一段”,F(xiàn)在開始分析:

在 AbstractBeanFactory 類 doGetBean 方法,有這么一段:

// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
    // Not found -> check parent.
    String nameToLookup = originalBeanName(name);
    if (args != null) {
            // Delegation to parent with explicit args.
        return (T) parentBeanFactory.getBean(nameToLookup, args);
    }
    else {
        // No args -> delegate to standard getBean method.
        return parentBeanFactory.getBean(nameToLookup, requiredType);
    }
}

這里其實(shí)是在獲取父容器中的 Bean,若獲取到,直接拿到 Bean,這個(gè)方法就結(jié)束了。結(jié)論:子容器可以使用父容器里的 Bean,反之則不行。


現(xiàn)在來解答咱們的問題

<bean class="com.foo.service.processor.PrintTimeProcessor"/>

當(dāng)上門這句話放在 springmvc.xml 中時(shí),名為?“printTimeProcessor”?的 Bean 會(huì)存在于 SpringMVC 容器,那么 Spring 容器是無法獲取它的。而 Service 層恰巧是存在于 Spring 容器中,所以?“printTimeProcessor”?切面對(duì) Service 層不起作用。而 Controller 層本身存在于 SpringMVC 容器,所以 Controller 層可以正常工作。而當(dāng)它放在 spring.xml 中時(shí),”printTimeProcessor” 是存在于 Spring 容器中,SpringMVC 容器是 Spring 容器的子容器,子容器可以獲取到父容器的 Bean,所以 Controller 層與 Service 層都能獲取到該 Bean,所有都能正常使用它。

標(biāo)簽: isp 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:Java中的構(gòu)造函數(shù)引用和方法引用

下一篇:常被問到的十個(gè) Java 面試題