slf4j - Simple Logging Facade for Java
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.4.8version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>2.0.7version>
dependency>
23:28:04,608 |-INFO in ch.qos.logback.classic.LoggerContext[LOGBACK] - This is logback-classic version 1.4.8
23:28:04,631 |-INFO in ch.qos.logback.classic.LoggerContext[LOGBACK] - Could NOT find resource [logback-test.xml]
23:28:04,637 |-INFO in ch.qos.logback.classic.LoggerContext[LOGBACK] - Found resource [logback.xml] at [file:/D:/Code/IdeaProjects/logback-demo/target/classes/logback.xml]
23:28:04,738 |-INFO in ch.qos.logback.classic.model.processor.ContextNameModelHandler - Setting logger context name as [LOGBACK]
23:28:04,738 |-INFO in ch.qos.logback.core.model.processor.TimestampModelHandler - Using current interpretation time, i.e. now, as time reference.
23:28:04,763 |-INFO in ch.qos.logback.core.model.processor.TimestampModelHandler - Adding property to the context with key="bySecond" and value="20230907T232804" to the LOCAL scope
23:31:05,485 |-INFO in ch.qos.logback.core.model.processor.AppenderModelHandler - Processing appender named [STDOUT]
23:31:05,487 |-INFO in ch.qos.logback.core.model.processor.AppenderModelHandler - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
23:31:05,620 |-INFO in ch.qos.logback.core.model.processor.ImplicitModelHandler - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
23:31:12,891 |-INFO in ch.qos.logback.core.model.processor.AppenderModelHandler - Processing appender named [FILE]
23:31:12,891 |-INFO in ch.qos.logback.core.model.processor.AppenderModelHandler - About to instantiate appender of type [ch.qos.logback.core.FileAppender]
23:31:12,901 |-INFO in ch.qos.logback.core.model.processor.ImplicitModelHandler - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
23:31:12,903 |-INFO in ch.qos.logback.core.FileAppender[FILE] - File property is set to [log-20230907T232804.txt]
23:31:13,781 |-WARN in ch.qos.logback.core.model.processor.AppenderModelHandler - Appender named [FILE_ROLLING] not referenced. Skipping further processing.
23:31:14,885 |-INFO in ch.qos.logback.classic.model.processor.RootLoggerModelHandler - Setting level of ROOT logger to DEBUG
23:31:14,922 |-INFO in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Attaching appender named [STDOUT] to Logger[ROOT]
23:31:14,924 |-INFO in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Attaching appender named [FILE] to Logger[ROOT]
23:31:32,051 |-INFO in ch.qos.logback.core.model.processor.DefaultProcessor@61001b64 - End of configuration.
23:32:01,939 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@4310d43 - Registering current configuration as safe fallback point
public final class LoggerFactory {
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
return getProvider().getLoggerFactory();
}
static SLF4JServiceProvider getProvider() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return PROVIDER;
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_SERVICE_PROVIDER;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_PROVIDER;
}
throw new IllegalStateException("Unreachable code");
}
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
}
/**
* 先通过 ServiceLoader 的方式去查找 SLF4JServiceProvider 的实现类,并取第一个实现类, 当前版本 logback 则是通过这种方式来加载 ch.qos.logback.classic.spi.LogbackServiceProvider 类
* 若第一种方式没找到实现类,则通过类加载加载类资源的方式加载 org/slf4j/impl/StaticLoggerBinder.class, log4j则是用过这种方式来加载
*/
private final static void bind() {
try {
List<SLF4JServiceProvider> providersList = findServiceProviders();
reportMultipleBindingAmbiguity(providersList);
if (providersList != null && !providersList.isEmpty()) {
PROVIDER = providersList.get(0);
// SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
PROVIDER.initialize();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(providersList);
} else {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("No SLF4J providers were found.");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_PROVIDERS_URL + " for further details.");
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
}
postBindCleanUp();
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
// Package access for tests
static List<SLF4JServiceProvider> findServiceProviders() {
// retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that
// loaded the present class to search for services
final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader();
ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);
List<SLF4JServiceProvider> providerList = new ArrayList<>();
Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
safelyInstantiate(providerList, iterator);
}
return providerList;
}
private static ServiceLoader<SLF4JServiceProvider> getServiceLoader(final ClassLoader classLoaderOfLoggerFactory) {
ServiceLoader<SLF4JServiceProvider> serviceLoader;
SecurityManager securityManager = System.getSecurityManager();
if(securityManager == null) {
serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory);
} else {
final PrivilegedAction<ServiceLoader<SLF4JServiceProvider>> action = () -> ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory);
serviceLoader = AccessController.doPrivileged(action);
}
return serviceLoader;
}
// We need to use the name of the StaticLoggerBinder class, but we can't
// reference the class itself.
private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
public class LogbackServiceProvider implements SLF4JServiceProvider {
@Override
public void initialize() {
defaultLoggerContext = new LoggerContext();
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
initializeLoggerContext();
defaultLoggerContext.start();
markerFactory = new BasicMarkerFactory();
mdcAdapter = new LogbackMDCAdapter();
// set the MDCAdapter for the defaultLoggerContext immediately
defaultLoggerContext.setMDCAdapter(mdcAdapter);
}
private void initializeLoggerContext() {
try {
try {
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// LOGBACK-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
// contextSelectorBinder.init(defaultLoggerContext, KEY);
} catch (Exception t) { // see LOGBACK-1159
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
private void initializeLoggerContext() {
try {
try {
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// LOGBACK-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
// contextSelectorBinder.init(defaultLoggerContext, KEY);
} catch (Exception t) { // see LOGBACK-1159
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
}
public class ContextInitializer {
public void autoConfig() throws JoranException {
autoConfig(Configurator.class.getClassLoader());
}
public void autoConfig(ClassLoader classLoader) throws JoranException {
String versionStr = EnvUtil.logbackVersion();
if (versionStr == null) {
versionStr = CoreConstants.NA;
}
loggerContext.getStatusManager().add(new InfoStatus(CoreConstants.LOGBACK_CLASSIC_VERSION_MESSAGE + versionStr, loggerContext));
StatusListenerConfigHelper.installIfAsked(loggerContext);
List<Configurator> configuratorList = ClassicEnvUtil.loadFromServiceLoader(Configurator.class, classLoader);
sortByPriority(configuratorList);
// this should never happen as we do have DefaultJoranConfigurator shipping with logback-classic
if (configuratorList == null) {
fallbackOnToBasicConfigurator();
return;
}
for (Configurator c : configuratorList) {
try {
c.setContext(loggerContext);
Configurator.ExecutionStatus status = c.configure(loggerContext);
if (status == Configurator.ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY) {
return;
}
} catch (Exception e) {
throw new LogbackException(
String.format("Failed to initialize Configurator: %s using ServiceLoader",
c != null ? c.getClass().getCanonicalName() : "null"),
e);
}
}
// at this stage invoke basicConfigurator
fallbackOnToBasicConfigurator();
}
}
public class DefaultJoranConfigurator extends ContextAwareBase implements Configurator {
final public static String AUTOCONFIG_FILE = "logback.xml";
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
/**
* 1. 获取配置文件 url
* 2.
*/
@Override
public ExecutionStatus configure(LoggerContext loggerContext) {
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
try {
configureByResource(url);
} catch (JoranException e) {
e.printStackTrace();
}
// we tried and that counts Mary.
return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
} else {
return ExecutionStatus.INVOKE_NEXT_IF_ANY;
}
}
}
获取配置文件 url
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
private URL getResource(String filename, ClassLoader myClassLoader, boolean updateStatus) {
URL url = Loader.getResource(filename, myClassLoader);
if (updateStatus) {
statusOnResourceSearch(filename, myClassLoader, url);
}
return url;
}
根据配置文件进行配置
public void configureByResource(URL url) throws JoranException {
if (url == null) {
throw new IllegalArgumentException("URL argument cannot be null");
}
final String urlString = url.toString();
if (urlString.endsWith("xml")) {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
configurator.doConfigure(url);
} else {
throw new LogbackException(
"Unexpected filename extension of file [" + url.toString() + "]. Should be .xml");
}
}
public abstract class GenericXMLConfigurator extends ContextAwareBase {
public final void doConfigure(URL url) throws JoranException {
InputStream in = null;
try {
informContextOfURLUsedForConfiguration(getContext(), url);
URLConnection urlConnection = url.openConnection();
// per http://jira.qos.ch/browse/LBCORE-105
// per http://jira.qos.ch/browse/LBCORE-127
urlConnection.setUseCaches(false);
in = urlConnection.getInputStream();
doConfigure(in, url.toExternalForm());
} catch (IOException ioe) {
String errMsg = "Could not open URL [" + url + "].";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
String errMsg = "Could not close input stream";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
}
}
}
}
public final void doConfigure(InputStream inputStream, String systemId) throws JoranException {
InputSource inputSource = new InputSource(inputStream);
inputSource.setSystemId(systemId);
doConfigure(inputSource);
}
// this is the most inner form of doConfigure whereto other doConfigure
// methods ultimately delegate
public final void doConfigure(final InputSource inputSource) throws JoranException {
context.fireConfigurationEvent(newConfigurationStartedEvent(this));
long threshold = System.currentTimeMillis();
// 利用 sax(Simple api for XML)逐行解析配置文件
SaxEventRecorder recorder = populateSaxEventRecorder(inputSource);
List<SaxEvent> saxEvents = recorder.getSaxEventList();
if (saxEvents.isEmpty()) {
addWarn("Empty sax event list");
return;
}
// 将解析到的xml配置封装成 各种元素的Model
Model top = buildModelFromSaxEventList(recorder.getSaxEventList());
if (top == null) {
addError(ErrorCodes.EMPTY_MODEL_STACK);
return;
}
sanityCheck(top);
// 处理 Model
processModel(top);
// no exceptions at this level
StatusUtil statusUtil = new StatusUtil(context);
if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
addInfo("Registering current configuration as safe fallback point");
registerSafeConfiguration(top);
}
context.fireConfigurationEvent(newConfigurationEndedEvent(this));
}
public void processModel(Model model) {
buildModelInterpretationContext();
DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
addModelHandlerAssociations(defaultProcessor);
// disallow simultaneous configurations of the same context
synchronized (context.getConfigurationLock()) {
defaultProcessor.process(model);
}
}
}
添加各种ModelHandler用于处理Model
public class JoranConfigurator extends JoranConfiguratorBase<ILoggingEvent> {
@Override
protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
super.addModelHandlerAssociations(defaultProcessor);
defaultProcessor.addHandler(ConfigurationModel.class, ConfigurationModelHandler::makeInstance);
defaultProcessor.addHandler(ContextNameModel.class, ContextNameModelHandler::makeInstance);
defaultProcessor.addHandler(LoggerContextListenerModel.class, LoggerContextListenerModelHandler::makeInstance);
defaultProcessor.addHandler(InsertFromJNDIModel.class, InsertFromJNDIModelHandler::makeInstance);
defaultProcessor.addHandler(AppenderModel.class, AppenderModelHandler::makeInstance);
defaultProcessor.addHandler(AppenderRefModel.class, AppenderRefModelHandler::makeInstance);
defaultProcessor.addHandler(RootLoggerModel.class, RootLoggerModelHandler::makeInstance);
defaultProcessor.addHandler(LoggerModel.class, LoggerModelHandler::makeInstance);
defaultProcessor.addHandler(LevelModel.class, LevelModelHandler::makeInstance);
defaultProcessor.addAnalyser(LoggerModel.class,
() -> new RefContainerDependencyAnalyser(context, LoggerModel.class));
defaultProcessor.addAnalyser(RootLoggerModel.class,
() -> new RefContainerDependencyAnalyser(context, RootLoggerModel.class));
defaultProcessor.addAnalyser(AppenderModel.class,
() -> new RefContainerDependencyAnalyser(context, AppenderModel.class));
defaultProcessor.addAnalyser(AppenderRefModel.class,
() -> new AppenderRefDependencyAnalyser(context));
sealModelFilters(defaultProcessor);
}
}
这里可以看到并不是直接实例化一个 ModelHandler 然后添加进去,而是放入了一个 ModelHandlerFactoryMethod。ModelHandlerFactoryMethod 是一个 @FunctionalInterface,专门用于实例化对应的 ModelHandler
@FunctionalInterface
public interface ModelHandlerFactoryMethod {
public ModelHandlerBase make(Context context, ModelInterpretationContext ic);
}
这里体现了一个懒加载的思想,当需要处理某种 Model 时,才去实例化对应的 ModelHandler,每个 ModelHandler 都有一个实例化的方法,如 AppenderModelHandler
public class AppenderModelHandler<E> extends ModelHandlerBase {
static public ModelHandlerBase makeInstance(Context context, ModelInterpretationContext mic) {
return new AppenderModelHandler(context);
}
}
父类 JoranConfiguratorBase 添加 ModelHandler
abstract public class JoranConfiguratorBase<E> extends GenericXMLConfigurator {
@Override
protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
defaultProcessor.addHandler(ImportModel.class, ImportModelHandler::makeInstance);
defaultProcessor.addHandler(ShutdownHookModel.class, ShutdownHookModelHandler::makeInstance);
defaultProcessor.addHandler(SequenceNumberGeneratorModel.class, SequenceNumberGeneratorModelHandler::makeInstance);
defaultProcessor.addHandler(EventEvaluatorModel.class, EventEvaluatorModelHandler::makeInstance);
defaultProcessor.addHandler(DefineModel.class, DefineModelHandler::makeInstance);
defaultProcessor.addHandler(IncludeModel.class, NOPModelHandler::makeInstance);
defaultProcessor.addHandler(ParamModel.class, ParamModelHandler::makeInstance);
defaultProcessor.addHandler(PropertyModel.class, PropertyModelHandler::makeInstance);
defaultProcessor.addHandler(TimestampModel.class, TimestampModelHandler::makeInstance);
defaultProcessor.addHandler(StatusListenerModel.class, StatusListenerModelHandler::makeInstance);
defaultProcessor.addHandler(ImplicitModel.class, ImplicitModelHandler::makeInstance);
defaultProcessor.addHandler(IfModel.class, IfModelHandler::makeInstance);
defaultProcessor.addHandler(ThenModel.class, ThenModelHandler::makeInstance);
defaultProcessor.addHandler(ElseModel.class, ElseModelHandler::makeInstance);
defaultProcessor.addHandler(SiftModel.class, SiftModelHandler::makeInstance);
}
}
添加后的结果
phaseOneFilter 一共有 21 个 ModelFilter
phaseTwoFilter 一个5个
addHandler()
public class AllowModelFilter implements ModelFilter {
final Class<? extends Model> allowedModelType;
AllowModelFilter(Class<? extends Model> allowedType) {
this.allowedModelType = allowedType;
}
@Override
public FilterReply decide(Model model) {
if (model.getClass() == allowedModelType) {
return FilterReply.ACCEPT;
}
return FilterReply.NEUTRAL;
}
}
determineProcessingPhase()
Model 上的 @PhaseIndicator 注解标注了该 Model 是属于第几阶段处理的,没标注的默认为第一阶段。其中,LoggerModel, RootLoggerModel, AppenderModel, AppenderRefModel 这四个属于第二条过滤器链
addAnalyser()
将 ModelHandler 添加到 modelClassToDependencyAnalyserMap,这些 ModelHandler 专门用于解析依赖
process()
主处理流程
mainTraverse()
public class DefaultProcessor extends ContextAwareBase {
final protected ModelInterpretationContext mic;
final HashMap<Class<? extends Model>, ModelHandlerFactoryMethod> modelClassToHandlerMap = new HashMap<>();
final HashMap<Class<? extends Model>, Supplier<ModelHandlerBase>> modelClassToDependencyAnalyserMap = new HashMap<>();
ChainedModelFilter phaseOneFilter = new ChainedModelFilter();
ChainedModelFilter phaseTwoFilter = new ChainedModelFilter();
public void addHandler(Class<? extends Model> modelClass, ModelHandlerFactoryMethod modelFactoryMethod) {
modelClassToHandlerMap.put(modelClass, modelFactoryMethod);
ProcessingPhase phase = determineProcessingPhase(modelClass);
switch (phase) {
case FIRST:
getPhaseOneFilter().allow(modelClass);
break;
case SECOND:
getPhaseTwoFilter().allow(modelClass);
break;
default:
throw new IllegalArgumentException("unexpected value " + phase + " for model class " + modelClass.getName());
}
}
private ProcessingPhase determineProcessingPhase(Class<? extends Model> modelClass) {
PhaseIndicator phaseIndicator = modelClass.getAnnotation(PhaseIndicator.class);
if (phaseIndicator == null) {
return ProcessingPhase.FIRST;
}
ProcessingPhase phase = phaseIndicator.phase();
return phase;
}
public void addAnalyser(Class<? extends Model> modelClass, Supplier<ModelHandlerBase> analyserSupplier) {
modelClassToDependencyAnalyserMap.put(modelClass, analyserSupplier);
}
public void process(Model model) {
if (model == null) {
addError("Expecting non null model to process");
return;
}
initialObjectPush();
mainTraverse(model, getPhaseOneFilter());
analyseDependencies(model);
traversalLoop(this::secondPhaseTraverse, model, getPhaseTwoFilter(), "phase 2");
addInfo("End of configuration.");
finalObjectPop();
}
// 递归处理树形结构的xml配置文件
protected int mainTraverse(Model model, ModelFilter modelFiler) {
FilterReply filterReply = modelFiler.decide(model);
if (filterReply == FilterReply.DENY)
return DENIED;
int count = 0;
try {
ModelHandlerBase handler = null;
boolean unhandled = model.isUnhandled();
if (unhandled) {
handler = createHandler(model);
if (handler != null) {
handler.handle(mic, model);
model.markAsHandled();
count++;
}
}
// recurse into submodels handled or not
if (!model.isSkipped()) {
for (Model m : model.getSubModels()) {
count += mainTraverse(m, modelFiler);
}
}
if (unhandled && handler != null) {
handler.postHandle(mic, model);
}
} catch (ModelHandlerException e) {
addError("Failed to traverse model " + model.getTag(), e);
}
return count;
}
private void traversalLoop(TraverseMethod traverseMethod, Model model, ModelFilter modelfFilter, String phaseName) {
int LIMIT = 3;
for (int i = 0; i < LIMIT; i++) {
int handledModelCount = traverseMethod.traverse(model, modelfFilter);
if (handledModelCount == 0)
break;
}
}
protected int secondPhaseTraverse(Model model, ModelFilter modelFilter) {
FilterReply filterReply = modelFilter.decide(model);
if (filterReply == FilterReply.DENY) {
return 0;
}
int count = 0;
try {
boolean allDependenciesStarted = allDependenciesStarted(model);
ModelHandlerBase handler = null;
if (model.isUnhandled() && allDependenciesStarted) {
handler = createHandler(model);
if (handler != null) {
handler.handle(mic, model);
model.markAsHandled();
count++;
}
}
if (!allDependenciesStarted && !dependencyIsADirectSubmodel(model)) {
return count;
}
if (!model.isSkipped()) {
for (Model m : model.getSubModels()) {
count += secondPhaseTraverse(m, modelFilter);
}
}
if (handler != null) {
handler.postHandle(mic, model);
}
} catch (ModelHandlerException e) {
addError("Failed to traverse model " + model.getTag(), e);
}
return count;
}
}
配置元素的抽象表示
带有@PhaseIndicator的Model,标明了为第二阶段解析