从Spring源码⾓度分析bug
结合源码分析 Spring 容器与 SpringMVC 容器之间的关系
问题
问题描述:项⽬中发现,⾃定义切⾯注解在 Controller 层正常⼯作,在 Service 层却⽆法正常⼯作。为了便于分析,去掉代码中的业务逻辑,只留下场景。
⾃定义注解,打印时间
/**
* Description: ⾃定义打印时间的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface PrintTime {
}
注解解析器
/**
*Description:打印时间注解的解析器
*/
@Aspect
public class PrintTimeProcessor {
private Logger LOGGER = Logger(getClass());
@Pointcut("@annotation(com.foo.service.annotation.PrintTime)")
public void printTimePoint() {
}
@Around("printTimePoint()")
public Object process(ProceedingJoinPoint jp) throws Throwable{
System.out.println();
<("开始运⾏程序。。。Start==>");
Object proceed = jp.proceed();
<("结束啦,运⾏结束==>");
System.out.println();
return proceed;
}
}
Controller层
@RestController
@RequestMapping(value = "/user")
public class UserController {
private Logger logger = Logger(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层
@Service
public class UserService {
private Logger logger = Logger(getClass())
@PrintTime
public String serviceAspect(){
logger.info("Service层---测试切⾯");
return "serviceAspect";
}
}
<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:l" />
<mvc:annotation-driven />
<mvc:default-servlet-handler />
<!-- 动态代理开启 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- mvc controller -->
<context:component-scan base-package="com.ller" 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"/>
以上为主要代码。项⽬运⾏之后,发现在 Service 层的注解切⾯未⽣效,⽽在 Controller 层正常。⽽当我将 l 中的
<bean class="com.foo.service.processor.PrintTimeProcessor"/>
迁移⾄ l 中,发现 Service 层与 Controller 层的注解切⾯均可正常运⾏。WHY
从源码的⾓度探究该问题
由于源码中的⽅法较长,所以只贴出重点且与主题相关的代码。建议结合本地源码⼀起看。
为了说清楚这个问题,咱们先看⼀下Spring容器是如何实现 Bean ⾃动注⼊(简化版)Web 项⽬的⼊⼝是 l,所以咱们从它开始。l 配置⽂件,主要部分
<!-- Spring Config -->
<listener>
<listener-class>org.t.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:l</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>
spring aop应用场景<param-value>classpath:l</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 是 Spring 容器的⼊⼝,进⼊该⽂件:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
@Override
public void contextInitialized(ServletContextEvent event) {
ServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
ServletContext());
ContextCleanupListener.ServletContext());
}
}
ContextLoaderListener ⼀共有四个⽅法,可以很容易地判断出来,进⼊该后,会进⼊初始化⽅法:contextInitialized。继⽽进⼊ initWebApplicationContext ⽅法,⽅法注释中 “Initialize Spring’s web application context for the given servlet context”,明确表明了该⽅法的⽬的是初始化 Spring Web 应⽤。这段代码中有两句话⽐较关键:
创建 Web 应⽤容器,即创建了 Spring 容器;
configureAndRefreshWebApplicationContext(cwac, servletContext);
配置并刷新Spring容器。后续发⽣的所有事,都是从它开始的。进⼊,⾥⾯的重点代码是:
refresh() ⽅法是spring容器注⼊bean的核⼼⽅法,每⼀⾏代码都很重要。代码结构也⾮常优美,每⼀⾏代码背后都完成了⼀件事,代码结构⽐较容易理解。由于内容较多,只讲⾥⾯跟主题相关的两句话:
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
获取 Bean ⼯⼚,把你配置⽂件中的内容,放在 Bean ⼯⼚中,留着后⾯创建 Bean 时⽤。
finishBeanFactoryInitialization(beanFactory);
开始创建 Bean,即实现 Spring 中的⾃动注⼊功能。进⼊该⽅法后,末尾有这么⼀句话:
beanFactory.preInstantiateSingletons();
继续跟进,贴出该⽅法中的重点代码:
getBean(beanName);
我们在 preInstantiateSingletons() ⽅法中,会发现有多个地⽅出现了 getBean() ⽅法,究竟咱们贴出来的是哪⼀句?⽆关紧要。跟进去之后,
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
这⾥调⽤了 doGetBean() ⽅法,Spring 中只要以 do 命名的⽅法,都是真正⼲活的。重点代码分段贴出分析:
// 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,若没有取到,继续往下⾛:
/
/ 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) Bean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
Bean(nameToLookup, requiredType);
}
}
这⼀段代码单独看,不知所云,⾥⾯提到了⼀个词:Parent。暂且跳过,后续会回来分析这⼀段。继续:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论