Skip to content

Is there a recommended approach to overriding the DefaultMessageFactory? #172

@beirtipol

Description

@beirtipol

Using

  • Spring Boot 3.0.2
  • quickfixj-spring-boot-starter 2.15.2

The quickfixj DefaultMessageFactory performs a lot of classloader scanning during startup for all potential FIX versions which greatly impacts startup performance.

Here's the snippet from quickfixj DefaultMessageFactory. The slow calls are during the Class.forName reflection calls

    /**
     * Constructs a DefaultMessageFactory, which dynamically loads and delegates to
     * the default version-specific message factories, if they are available at runtime.
     * <p>
     * Callers can set the {@link Thread#setContextClassLoader context classloader},
     * which will be used to load the classes if {@link Class#forName Class.forName}
     * fails to do so (e.g. in an OSGi environment).
     * <p>
     * Equivalent to {@link #DefaultMessageFactory(String) DefaultMessageFactory}({@link ApplVerID#FIX50 ApplVerID.FIX50}).
     */
    public DefaultMessageFactory() {
        this(ApplVerID.FIX50SP2);
    }

    /**
     * Constructs a DefaultMessageFactory, which dynamically loads and delegates to
     * the default version-specific message factories, if they are available at runtime.
     * <p>
     * Callers can set the {@link Thread#setContextClassLoader context classloader},
     * which will be used to load the classes if {@link Class#forName Class.forName}
     * fails to do so (e.g. in an OSGi environment).
     *
     * @param defaultApplVerID ApplVerID value used by default for {@link #create(String, ApplVerID, String)}
     */
    public DefaultMessageFactory(String defaultApplVerID) {
        Objects.requireNonNull(defaultApplVerID, "defaultApplVerID");

        this.defaultApplVerID = new ApplVerID(defaultApplVerID);

        // To loosen the coupling between this factory and generated code, the
        // message factories are discovered at run time using reflection
        addFactory(BEGINSTRING_FIX40);
        addFactory(BEGINSTRING_FIX41);
        addFactory(BEGINSTRING_FIX42);
        addFactory(BEGINSTRING_FIX43);
        addFactory(BEGINSTRING_FIX44);
        addFactory(BEGINSTRING_FIXT11);
        addFactory(FIX50);
        addFactory(FIX50SP1);
        addFactory(FIX50SP2);
    }

    private void addFactory(String beginString) {
        String packageVersion = beginString.replace(".", "").toLowerCase();
        try {
            addFactory(beginString, "quickfix." + packageVersion + ".MessageFactory");
        } catch (ClassNotFoundException e) {
            // ignore - this factory is not available
        }
    }

    /**
     * Adds a factory of the given class, which will be delegated to for creating
     * Message instances from messages with the given begin string.
     * <p>
     * Callers can set the {@link Thread#setContextClassLoader context classloader},
     * which will be used to load the classes if {@link Class#forName Class.forName}
     * fails to do so (e.g. in an OSGi environment).
     *
     * @param beginString the begin string whose messages will be delegated to the factory
     * @param factoryClassName the name of the factory class to instantiate and add
     * @throws ClassNotFoundException if the named factory class cannot be found
     * @throws RuntimeException if the named factory class cannot be instantiated
     */
    @SuppressWarnings("unchecked")
    public void addFactory(String beginString, String factoryClassName) throws ClassNotFoundException {
        // try to load the class
        Class<? extends MessageFactory> factoryClass = null;
        try {
            // try using our own classloader
            factoryClass = (Class<? extends MessageFactory>) Class.forName(factoryClassName);
        } catch (ClassNotFoundException e) {
                // try using context classloader (i.e. allow caller to specify it)
            Thread.currentThread().getContextClassLoader().loadClass(factoryClassName);
        }
        // if factory is found, add it
        if (factoryClass != null) {
            addFactory(beginString, factoryClass);
        }
    }

An easy solution we've used in the past is to override the message factory with our own implementation which explicitly loads the required MessageFactories that are explicitly on the classpath for each BeginString.

In order to get the starter project to use it, we need to be able to override the clientMessageFactory, but this is currently set up using a bean name. In order to override this, we need to name our implementation "clientMessageFactory". I'm not a huge fan of this because this was inadvertently broken by a rename of the class.

Here's the call in this project's source
From QuickFixJClientAutoConfiguration

	/**
	 * Creates the default client's {@link MessageFactory}
	 *
	 * @return The default client's {@link MessageFactory application} bean
	 */
	@Bean
	@ConditionalOnMissingBean(name = "clientMessageFactory")
	public MessageFactory clientMessageFactory() {
		return new DefaultMessageFactory();
	}

Perhaps a property could be exposed to allow the user to specifically override this bean?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions