0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

About injecting beans managed by Spring Framework into Apache Wicket Component

Posted at

Assumption

  • Using wicket-spring-boot-starter, which include Spring Boot 2.6.1 and Apache Wicket 9.7.0
  • ApplicationContext is successfully created in some place of the program

How to use

If you want beans managed by Spring Framework inject into Component, you may annotate the field with @org.apache.wicket.spring.injection.annot.SpringBean. (OR @javax.inject.Inject also works) Below is a example.

public class Product extends WebPage {
	private static final long serialVersionUID = 1L;  // Serializable interface
	@SpringBean
	private Dao dao;

	// other method...
}

Something you need to know

  • all Component is managed by Apache Wicket, not by Spring Framework. So injection is performed by Apache Wicket also.
  • the instance injected is a proxy instance.
  • Component implementes Serializable interface

What happen inside

First, Apache Wicket creates Component by calling its constructor.

public abstract class Component implements
IClusterable, IConverterLocator, IRequestableComponent, IHeaderContributor, IHierarchical<Component>, IEventSink, IEventSource, IMetadataContext<Serializable, Component>, IFeedbackContributor
{
	//...
	public Component(final String id, final IModel<?> model)
	{
		checkId(id);
		this.id = id;

		init();

		Application application = getApplication();
		// <carries out injection>
		application.getComponentInstantiationListeners().onInstantiation(this);
		//

		final DebugSettings debugSettings = application.getDebugSettings();
		if (debugSettings.isLinePreciseReportingOnNewComponentEnabled() && debugSettings.getComponentUseCheck())
		{
			setMetaData(CONSTRUCTED_AT_KEY,
				ComponentStrings.toString(this, new MarkupException("constructed")));
		}

		if (model != null)
		{
			setModelImpl(wrap(model));
		}
	}
	//...

When calling application.getComponentInstantiationListeners().onInstantiation(this), finally org.apache.wicket.injection.Injector.inject(Object, IFieldValueFactory) will be called. Object of IFieldValueFactory is org.apache.wicket.spring.injection.annot.AnnotProxyFieldValueFactory.

public abstract class Injector {
	//...
	protected void inject(final Object object, final IFieldValueFactory factory)
	{
		final Class<?> clazz = object.getClass();

		Field[] fields = null;

		// try cache
		fields = cache.get(clazz);

		if (fields == null)
		{
			// cache miss, discover fields
			fields = findFields(clazz, factory);

			// write to cache
			cache.put(clazz, fields);
		}

		for (final Field field : fields)
		{
			if (!field.canAccess(object))
			{
				field.setAccessible(true);
			}
			try
			{

				if (field.get(object) == null)
				{
					// <get bean from Spring Framework>
					Object value = factory.getFieldValue(field, object);
					//

					if (value != null)
					{
						field.set(object, value);
					}
				}
			}
			catch (IllegalArgumentException e)
			{
				throw new RuntimeException("error while injecting object [" + object.toString() +
					"] of type [" + object.getClass().getName() + "]", e);
			}
			catch (IllegalAccessException e)
			{
				throw new RuntimeException("error while injecting object [" + object.toString() +
					"] of type [" + object.getClass().getName() + "]", e);
			}
		}
	}
	//...

factory.getFieldValue(field, object) is org.apache.wicket.spring.injection.annot.AnnotProxyFieldValueFactory.getFieldValue(Field, Object).

public class AnnotProxyFieldValueFactory implements IFieldValueFactory {
	//...
	@Override
	public Object getFieldValue(final Field field, final Object fieldOwner)
	{
		if (supportsField(field))
		{
			SpringBean annot = field.getAnnotation(SpringBean.class);

			String name;
			boolean required;
			if (annot != null)
			{
				name = annot.name();
				required = annot.required();
			}
			else
			{
				Named named = field.getAnnotation(Named.class);
				name = named != null ? named.value() : "";
				required = true;
			}

			Class<?> generic = ResolvableType.forField(field).resolveGeneric(0);
			String beanName = getBeanName(field, name, required, generic);

			SpringBeanLocator locator = new SpringBeanLocator(beanName, field.getType(), field, contextLocator);

			// only check the cache if the bean is a singleton
			Object cachedValue = cache.get(locator);
			if (cachedValue != null)
			{
				return cachedValue;
			}

			Object target;
			try
			{
				// check whether there is a bean with the provided properties
				target = locator.locateProxyTarget();
			}
			catch (IllegalStateException isx)
			{
				if (required)
				{
					throw isx;
				}
				else
				{
					return null;
				}
			}

			if (wrapInProxies)
			{
				// <actual point of get bean and then wrap it by proxy>
				target = LazyInitProxyFactory.createProxy(field.getType(), locator);
				//
			}

			// only put the proxy into the cache if the bean is a singleton
			if (locator.isSingletonBean())
			{
				Object tmpTarget = cache.putIfAbsent(locator, target);
				if (tmpTarget != null)
				{
					target = tmpTarget;
				}
			}
			return target;
		}
		return null;
	}
	//...

Bean is obtained in org.apache.wicket.spring.SpringBeanLocator.lookupSpringBean(ApplicationContext, String, Class<?>).

	private Object lookupSpringBean(ApplicationContext ctx, String name, Class<?> clazz)
	{
		try
		{
			// If the name is set the lookup is clear
			if (name != null)
			{
				return ctx.getBean(name, clazz);
			}

			// If the beanField information is null the clazz is going to be used
			if (fieldResolvableType == null)
			{
				return ctx.getBean(clazz);
			}

			// If the given class is a list try to get the generic of the list
			Class<?> lookupClass = fieldElementsResolvableType != null ? 
				fieldElementsResolvableType.resolve() : clazz;

			// Else the lookup is done via Generic
			List<String> names = loadBeanNames(ctx, lookupClass);

			Object foundBeans = getBeansByName(ctx, names);

			if(foundBeans != null)
			{
				return foundBeans;
			}

			throw new IllegalStateException(
				"Concrete bean could not be received from the application context for class: " +
					clazz.getName() + ".");
		}
		catch (NoSuchBeanDefinitionException e)
		{
			throw new IllegalStateException("bean with name [" + name + "] and class [" +
				clazz.getName() + "] not found", e);
		}
	}

At here you can find out familiar org.springframework.beans.factory.BeanFactory.getBean() call.

Why wrapped by proxy

All components will be serialize at the end of request, therefore Component implementes Serializable interface. But the injected field is not serializable (at the worst case entire Spring Framework will be serialized), so the injected field is wrapped by a proxy. In the proxy, writeReplace() is defined for saving org.apache.wicket.proxy.LazyInitProxyFactory.ProxyReplacement instance instead of saving injected instance. (If you don't know what writeReplace() and readResolve() function is, please refer here)

In ProxyReplacement instance, necessary information for relocate the bean is saved. And the method readResolve() is defined for reinject the field again when deserialize the Component.

		private Object readResolve() throws ObjectStreamException
		{
			Class<?> clazz = WicketObjects.resolveClass(type);
			if (clazz == null)
			{
				try
				{
					clazz = Class.forName(type, false, Thread.currentThread().getContextClassLoader());
				}
				catch (ClassNotFoundException ignored1)
				{
					try
					{
						clazz = Class.forName(type, false, LazyInitProxyFactory.class.getClassLoader());
					}
					catch (ClassNotFoundException ignored2)
					{
						ClassNotFoundException cause = new ClassNotFoundException(
								"Could not resolve type [" + type +
										"] with the currently configured org.apache.wicket.application.IClassResolver");
						throw new WicketRuntimeException(cause);
					}
				}
			}
			return LazyInitProxyFactory.createProxy(clazz, locator);
		}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?