Bug analysis from the perspective of Spring Source

Analysis of the relationship between the binding source Spring container and the container SpringMVC

problem

Problem Description: The project found that custom aspects annotation layer to work in the Controller, but does not work in the Service layer. For analysis, business logic code removed, leaving only the scene.

Custom annotation, printing time

/**
 * Description: 自定义打印时间的注解
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface PrintTime {

}

Notes parser

/**
 *Description:打印时间注解的解析器
 */
@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("开始运行程序。。。Start==>");
        Object proceed = jp.proceed();
        LOGGER.error("结束啦,运行结束==>");
        System.out.println();
        return proceed;
    }
}

Controller layer

@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层----测试切面");
        return "controllerAspect";
    }
}

Service Layer

@Service
public class UserService {

    private Logger logger = LoggerFactory.getLogger(getClass())

    @PrintTime
    public String serviceAspect(){
        logger.info("Service层---测试切面");
        return "serviceAspect";
    }

}

spring.xml profile, the main part

<context:annotation-config />

<!-- 动态代理开启 -->

<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 profile, the main part

<mvc:annotation-driven />

<mvc:default-servlet-handler />

<!-- 动态代理开启 -->

<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"/>

The above is the main code. After running the project, we found not in force in the comment section Service layer, and the Controller layer normal. And when I was in the springmvc.xml

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

Migrating to spring.xml found comment section Service Layer and Controller layer can function properly. WHY ???

Explore the source of the problem from the perspective of

Due to the long method in the source code, so only put focus on the code and related topics. The proposed combination of watching a local source.

To clarify this issue, we look at how to achieve Bean Spring container is automatically injected into the inlet (Lite) Web project is web.xml, so let's start with it.

web.xml configuration file, the main part

<!-- 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>

Bean Spring container loading process

Spring configuration can be seen in section, the ContextLoaderListener listener Spring container is an inlet into the file:

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 listener a total of four methods can easily judge them, after entering the listener, will enter the initialization method: contextInitialized. Then enter initWebApplicationContext method, the method comment "Initialize Spring's web application context for the given servlet context", a clear indication of the purpose of the process is to initialize the Spring Web applications. There are two words in this code more critical:

this.context = createWebApplicationContext(servletContext);

Create a Web application container, which creates a Spring container;

configureAndRefreshWebApplicationContext(cwac, servletContext);

Configuration and refresh the Spring container. All subsequent things happen, it is from the beginning. Enter, which is the focus of the code:

wac.refresh();

refresh () method is spring bean core vessel injection methods, each line of code is important. Code structure is also very beautiful, behind each line of code to complete one thing, the structure of the code easier to understand. As more content, with which they talk about topics related to two sentences:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

Get Bean factory, the contents of your configuration files, Bean placed in the factory, keep the back when you create a Bean.

finishBeanFactoryInitialization(beanFactory);

Start creating Bean, Spring of achieving automatic injection function. After entering the process, at the end of it reads:

beanFactory.preInstantiateSingletons();

Continue to follow up, put focus on the code in the method:

getBean(beanName);

We preInstantiateSingletons () method, you will find several places appeared getBean () method, whether we stick out which sentence? It does not matter. After with the inside,

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

Here called doGetBean () method, Spring method to do as long as named, is the real work. Key segments posted the code analysis:

// 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);
}

Direct access to the singleton Bean, if not get to continue to go down:

// 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);
    }
}

This piece of code to see alone, incomprehensible, which refers to one word: Parent. Skip the time being, the subsequent analysis will come back to this period. carry on:

// 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);
}

This code has createBean, our aim is to analyze the process of creating the Bean, where there has been create, did not hesitate to follow up, to enter the implementation class method, there is such a sentence:

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

Let's just mention, Spring do have a named method, it is the real work. follow up:

instanceWrapper = createBeanInstance(beanName, mbd, args);

This sentence is initialized Bean, namely to create a Bean, equivalent to calling the empty constructor method of a class. In this case, the object has been successfully created, the following needs to be done is to inject the desired attributes of the object;

populateBean(beanName, mbd, instanceWrapper);

Bean filling properties, is it just mentioned, after the initialization of an object, just an empty object, it needs to fill properties. Follow up to see how Spring is injected into the subject property, or that, look at how Spring is automatically injected Bean attributes:

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

PostProcessPropertyValues ​​continue into the method of AutowiredAnnotationBeanPostProcessor:

metadata.inject(bean, beanName, pvs);

This sentence, there has been inject, the word means "inject." We can conclude, Spring automatic injection, most probably related to the saying. Enter the method:

element.inject(target, beanName, pvs); 

Like the previous one, just do some processing parameters, it did not start the injection. Continue to follow up to see:

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

See here, probably understand how Spring is automatically injected. Java reflection related code, assigned by the reflection of the way to the field. Bean field here is one of the attributes, e.g. UserController class at the beginning of our userService. getResourceToInject, given the need to get the value, in fact, there will re-enter getBean method to obtain Bean value (for example UserController object need to inject userService.), and then give the field. So far, Spring container has been initialized completed, Spring Bean injected about the process, we also already familiar with. Return to start initialization Spring container place, ContextLoader class initWebApplicationContext method,

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

After initialization Spring container, put it in a servletContext.

Our question is, "in the project, custom cut annotation layer to work in the Controller, but does not work in the Service Layer?" After reading this, did not actually answer the question, let's continue to look below the loading process SpringMVC Bean, after reading SpringMVC, the answer would automatically emerge.

SpringMVC Bean container loading process

Similarly, from the web.xml configuration SpringMVC departure, there DispatcherServlet, which is SpringMVC entrance and found many methods, can not know which method will be executed after the follow-up. But we have to remember that the DispatcherServlet essentially a Servlet, through its inheritance graph can be proved.

 

Look at the Servlet interface:

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 interface method can be seen, the inlet is Servlet init method, follow-up layers (must follow inheritance diagram according DispatcherServlet), into the initServletBean FrameworkServlet () method, a method to enter, posted key codes:

this.webApplicationContext = initWebApplicationContext();

Literally, initialization SpringMVC Web container, into the inquiry:

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

We mentioned front, after completion of initialization Spring container, placed in a servletContext. And from here servletContext get into the Spring container;

wac = createWebApplicationContext(rootContext);

Literally create a Web application container, and the parameter is the Spring container. Follow-up methods:

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

Create a web application container, that we understood in this container SpringMVC created;

wac.setParent(parent);

Here is the key, SpringMVC the Spring container vessel set became its parent container.

configureAndRefreshWebApplicationContext(wac);

This method is just in the analysis of Spring Bean load flow analysis before. One section, said earlier, "for the time being skipped, subsequent analysis will come back to this period." Now begin to analyze: the AbstractBeanFactory class doGetBean method, has this to say:

// 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);
    }
}

In fact, here is the Bean get the parent container, if acquired, to get direct Bean, this method is over. Conclusion: The child can use the parent container of container Bean, not vice versa.

Now to answer our questions

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

When the door in this sentence on springmvc.xml, Bean called "printTimeProcessor" SpringMVC will be present in the container, then the Spring container is unable to obtain it. Service layer and happens to be present in the Spring container, the "printTimeProcessor" Service cut facing layer does not work. Controller present in the layer itself SpringMVC container, the Controller layer may work. And when it is placed in spring.xml, "printTimeProcessor" Spring is present in the container, SpringMVC Spring container is a child container containers, sub-containers can get to Bean parent container, so the Controller layer and Service layer can be obtained the Bean, all of it can be used normally.

 

 

Original link: https://mp.weixin.qq.com/s/hJX9-lc4q2Uoc3eJNPHFdw

Published 142 original articles · won praise 345 · Views 450,000 +

Guess you like

Origin blog.csdn.net/zhengchao1991/article/details/85239483