跳到主要内容

14、Tomcat 源码解析 - Tomcat applicationAnnotationsConfig

这篇单独分析configureStart –> applicationAnnotationsConfig方法

protected synchronized void configureStart() {
        // Called from StandardContext.start()

        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.start"));
        }

        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.xmlSettings",
                    context.getName(),
                    Boolean.valueOf(context.getXmlValidation()),
                    Boolean.valueOf(context.getXmlNamespaceAware())));
        }

        webConfig();
        if (!context.getIgnoreAnnotations()) {
//StandardContext配置的ignoreAnnotations 为false,执行方法
            applicationAnnotationsConfig();
        }
        if (ok) {
            validateSecurityRoles();
        }

        // Configure an authenticator if we need one
        if (ok) {
            authenticatorConfig();
        }

       ………………………….
}

applicationAnnotationsConfig方法:

看源码发现

applicationAnnotationsConfig-------->WebAnnotationSet.loadApplicationAnnotations(context)------- à loadApplicationListenerAnnotations(context)| loadApplicationFilterAnnotations(context)| loadApplicationServletAnnotations(context)

1.loadApplicationListenerAnnotations方法*:*

protected static void loadApplicationListenerAnnotations(Context context) {
//ApplicationListeners是web.xml里面配置的Listener
        String[] applicationListeners = context.findApplicationListeners();
        for (String className : applicationListeners) {
// org.apache.catalina.loader.WebappLoader(digester)加载的Listener
            Class<?> classClass = Introspection.loadClass(context, className);
            if (classClass == null) {
                continue;
            }
//classClass是在web.xml中配置的Listener 对象
            loadClassAnnotation(context, classClass);
            loadFieldsAnnotation(context, classClass);
            loadMethodsAnnotation(context, classClass);
        }
}

2.loadApplicationFilterAnnotations方法:

protected static void loadApplicationFilterAnnotations(Context context) {
//跟loadApplicationListenerAnnotations方法类似,webXml配置的filter
        FilterDef[] filterDefs = context.findFilterDefs();
        for (FilterDef filterDef : filterDefs) {
// org.apache.catalina.loader.WebappLoader(digester)加载的filter
            Class<?> classClass = Introspection.loadClass(context,
                    filterDef.getFilterClass());
            if (classClass == null) {
                continue;
            }
//classClass是在web.xml中配置的filter 对象
            loadClassAnnotation(context, classClass);
            loadFieldsAnnotation(context, classClass);
            loadMethodsAnnotation(context, classClass);
        }
}

3、 loadApplicationServletAnnotations**方法:

protected static void loadApplicationServletAnnotations(Context context) {
//Wrapper是在webconfig方法  step9创建的默认是StandardWrapper,以后具体分析StandardWrapper
        Container[] children = context.findChildren();
        for (Container child : children) {
            if (child instanceof Wrapper) {
                Wrapper wrapper = (Wrapper) child;
                if (wrapper.getServletClass() == null) {
                    continue;
                }
//同样,是用org.apache.catalina.loader.WebappLoader(digester)加载的,是被Wrapper处理后的Servlet
                Class<?> classClass = Introspection.loadClass(context,
                        wrapper.getServletClass());
                if (classClass == null) {
                    continue;
                }

                loadClassAnnotation(context, classClass);
                loadFieldsAnnotation(context, classClass);
                loadMethodsAnnotation(context, classClass);
//最后会处理RunAs注解
                RunAs annotation = classClass.getAnnotation(RunAs.class);
                if (annotation != null) {
                    wrapper.setRunAs(annotation.value());
                }
            }
        }

发现上面三个方法最后都会调用三个方法

oadClassAnnotation、loadFieldsAnnotation和loadMethodsAnnotation

loadClassAnnotation方法:

读源码,loadClassAnnotation方法是读取类的各类Annotation,然后进行相应的处理。看到的Annotation有Resources 、 Resource 、EJB、WebServiceRef和DeclareRoles,但是就目前的源码来看,处理EJB和WebServiceRef的代码是注释掉的,上网查了注释里面的JSR,这里是连接https://en.wikipedia.org/wiki/JSR_250,将来可以去研究,现在只关注源码,最后处理Resource和DeclareRoles都会调用相应的方法,处理Resource的方法是addResource(Context context, Resource annotation,String defaultName, Class<?> defaultType),主要逻辑是通过annotation的type来分别实例化resource相关的类,最后都要context.getNamingResources().addEnvironment(resource),调用这行代码,添加到StandardContext的NamingResoures中,而处理DeclareRoles的主要逻辑是读取DeclareRoles的value,然后循环context.addSecurityRole(String role)

添加到securityRoles中。

loadFieldsAnnotation方法:

读取classClass上字段的Annotation Resource,调用context.addResource方法,不同的是会有defaultName和defaultType,defaultName=classClass.getName+/+field.getName,defaultType=field.getType

loadMethodsAnnotation方法:

读取classClass方法上的Annotation Resource,调用context.addResource方法,不同的是会有defaultName和defaultType,defaultName=classClass.getName+/+方法名,defaultType是该方法第一个参数的type,但是该方法要满足下面的条件

public static boolean isValidSetter(Method method) {

if (method.getName().startsWith("set")

        && method.getName().length() > 3

        && method.getParameterTypes().length == 1

        && method.getReturnType().getName().equals("void")) {

    return true;

}

return false;


validateSecurityRoles 方法:

protected void validateSecurityRoles() {
//Constraints,webConfig 方法step9 处理得到
        SecurityConstraint constraints[] = context.findConstraints();
        for (int i = 0; i < constraints.length; i++) {
            String roles[] = constraints[i].findAuthRoles();
//处理<security-constraint>配置的role
            for (int j = 0; j < roles.length; j++) {
//如果<security-constraint>配置的role不等于*并且不在SecurityRoles中,调用context.addSecurityRole
                if (!"*".equals(roles[j]) &&
                    !context.findSecurityRole(roles[j])) {
                    log.warn(sm.getString("contextConfig.role.auth", roles[j]));
                    context.addSecurityRole(roles[j]);
                }
            }
        }
//处理<servlet>配置的role
        Container wrappers[] = context.findChildren();
        for (int i = 0; i < wrappers.length; i++) {
            Wrapper wrapper = (Wrapper) wrappers[i];
            String runAs = wrapper.getRunAs();
            if ((runAs != null) && !context.findSecurityRole(runAs)) {
                log.warn(sm.getString("contextConfig.role.runas", runAs));
                context.addSecurityRole(runAs);
            }
            String names[] = wrapper.findSecurityReferences();
            for (int j = 0; j < names.length; j++) {
                String link = wrapper.findSecurityReference(names[j]);
                if ((link != null) && !context.findSecurityRole(link)) {
                    log.warn(sm.getString("contextConfig.role.link", link));
                    context.addSecurityRole(link);
                }
            }
        }

}

authenticatorConfig() 方法:

protected void authenticatorConfig() {
        LoginConfig loginConfig = context.getLoginConfig();
        SecurityConstraint constraints[] = context.findConstraints();
        if (context.getIgnoreAnnotations() &&
                (constraints == null || constraints.length ==0) &&
                !context.getPreemptiveAuthentication())  {
            return;
        } else {
//loginConfig是空,则默认使用new LoginConfig("NONE", null, null, null)
            if (loginConfig == null) {
                loginConfig = DUMMY_LOGIN_CONFIG;
                context.setLoginConfig(loginConfig);
            }
        }
//需要配置Authenticator,沒有配置就不会进行处理
        if (context.getAuthenticator() != null) {
            return;
        }
//必须给这个context配置realm,没配置则会报错
        if (context.getRealm() == null) {
            log.error(sm.getString("contextConfig.missingRealm"));
            ok = false;
            return;
        }

        /*
         * First check to see if there is a custom mapping for the login
         * method. If so, use it. Otherwise, check if there is a mapping in
         * org/apache/catalina/startup/Authenticators.properties.
         */
        Valve authenticator = null;
        if (customAuthenticators != null) {
            authenticator = (Valve) customAuthenticators.get(loginConfig.getAuthMethod());
        }

        if (authenticator == null) {
// authenticators static静态块中读取 org/apache/catalina/startup/Authenticators.properties文件得到的properties
            if (authenticators == null) {
                log.error(sm.getString("contextConfig.authenticatorResources"));
                ok = false;
                return;
            }

//通过该context的loginconfig配置的AuthMethod实例化类Authenticator
            String authenticatorName = 
authenticators.getProperty(loginConfig.getAuthMethod());
            if (authenticatorName == null) {
                log.error(sm.getString("contextConfig.authenticatorMissing",
                                 loginConfig.getAuthMethod()));
                ok = false;
                return;
            }
            try {
                Class<?> authenticatorClass = Class.forName(authenticatorName);
                authenticator = (Valve) authenticatorClass.newInstance();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString(
                                    "contextConfig.authenticatorInstantiate",
                                    authenticatorName),
                          t);
                ok = false;
            }
        }
//添加到valves中,可以看出这个authenticator在处理请求的时候发挥作用,请求处理后面分析
        if (authenticator != null) {
            Pipeline pipeline = context.getPipeline();
            if (pipeline != null) {
                pipeline.addValve(authenticator);
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString(
                                    "contextConfig.authenticatorConfigured",
                                    loginConfig.getAuthMethod()));
                }
            }
        }
}

到这里 ConfigureStart 方法我们粗略的执行逻辑已经分析完了,细节到处理到具体的时候再分析,其实查看源码后发现正确对于 Context 的正确顺序应该是 beforeinit->init->afterinit->beforestart->configurationstart->afterstart ,这么看来先看了 ConfigureStart 方法,因为其它事件处理逻辑简单,现在简单看下 ConfigContext lifecycle 的其他事件触发

CONFIGURE_STOP_EVENT -- à configureStop 方法主要是一些清理 code ,清理 configureStart 里面实例化的资源

AFTER_INIT_EVENT ----->init 方法 通过 Context 那篇分析的 contextRuleSet NamingRuleSet 解析处理 Context

AFTER_DESTROY_EVENT--- à destroy 方法 删除掉 workDir 下的 app,workDir=CatalinaBase\Work\Enginname\hostname\appname( 默认 )

 

1.loadApplicationListenerAnnotations方法*:*