Spring Boot 项目启动报错: Unable to start embedded

529 阅读1分钟
相关版本

Spring Boot 2.1.5.RELEASE

Spring AOP 1.5.7.RELEASE

CGLIB原理: 动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

  • 主要原因由于org.springframework.web.filter.GenericFilterBean的子类A的代理对象的logger属性为空引发的空指针异常

  • 分析

    1. AOP 注解@Aspect。对应切点配置的路径下类会由CGLIB(SpringBoot默认)动态代理生成.
    @Pointcut("execution(public * com.example.handler..*.*(..))")
    
    1. com.example.handler下的类A继承了OncePerRequestFilter

    2. 由于CGLIB原理, 生成类A的代理对象时, final声明的属性将为空

     public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
     	EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {
    
     /** Logger available to subclasses. */
     # ======> logger 属性声明为 final <======
     protected final Log logger = LogFactory.getLog(getClass());
    
     @Nullable
     private String beanName;
    
     @Nullable
     private Environment environment;
    
     @Nullable
     private ServletContext servletContext;
    
     @Nullable
     private FilterConfig filterConfig;
    
     private final Set<String> requiredProperties = new HashSet<>(4);
    
     略......
    
    1. GenericFilterBean执行init()方法到类A时,由于类A的代理对象的logger属性为空, 导致logger.isDebugEnabled()报空指针异常
    // org.springframework.web.filter.GenericFilterBean
    
    /**
     * Standard way of initializing this filter.
     * Map config parameters onto bean properties of this filter, and
     * invoke subclass initialization.
     * @param filterConfig the configuration for this filter
     * @throws ServletException if bean properties are invalid (or required
     * properties are missing), or if subclass initialization fails.
     * @see #initFilterBean
     */
    @Override
    public final void init(FilterConfig filterConfig) throws ServletException {
       Assert.notNull(filterConfig, "FilterConfig must not be null");
    
       this.filterConfig = filterConfig;
    
       // Set bean properties from init parameters.
       PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
       if (!pvs.isEmpty()) {
          try {
             BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
             ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
             Environment env = this.environment;
             if (env == null) {
                env = new StandardServletEnvironment();
             }
             bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
             initBeanWrapper(bw);
             bw.setPropertyValues(pvs, true);
          }
          catch (BeansException ex) {
             String msg = "Failed to set bean properties on filter '" +
                filterConfig.getFilterName() + "': " + ex.getMessage();
             logger.error(msg, ex);
             throw new NestedServletException(msg, ex);
          }
       }
    
       // Let subclasses do whatever initialization they like.
       initFilterBean();
    
       # ========> 由于 logger 为空, 此处出现空指针异常 <========
       if (logger.isDebugEnabled()) {
          logger.debug("Filter '" + filterConfig.getFilterName() + "' configured for use");
       }
    }
    
  • 解决方案

    • 将继承OncePerRequestFilter的类A移出对应包路径com.example.handler

    • 将对应 Filter 功能移到其他包下执行