vaadin / spring

Spring integration for Vaadin
https://vaadin.com/start
174 stars 101 forks source link

Using Beans in composite #526

Open F43nd1r opened 4 years ago

F43nd1r commented 4 years ago

I have a class like this

@SpringComponent
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class CardView extends Div {

    @Autowired
    public CardView(...some services...) {
        ...
    }
}

which I would like to use in a composite:

public class SpringCompositeDemo extends Composite<CardView> {

This is currently not possible. My workaround is to extend Composite:

@SpringComponent
public abstract class SpringComposite<T extends Component> extends Composite<T> {
    private ApplicationContext applicationContext;

    @SuppressWarnings("unchecked")
    @Override
    protected T initContent() {
        Class<? extends Component> contentType = findContentType((Class<? extends Composite<?>>) getClass());
        if (AnnotationUtils.findAnnotation(contentType, org.springframework.stereotype.Component.class) != null)
            if (applicationContext != null) {
                return (T) applicationContext.getBean(contentType);
            } else {
                throw new IllegalStateException("Cannot access Composite content before bean initialization");
            }
        return (T) ReflectTools.createInstance(contentType);
    }

    @Autowired
    public final void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /*
     * copied from Composite#findContentType(Class)
     */
    private static Class<? extends Component> findContentType(
            Class<? extends Composite<?>> compositeClass) {
        Type type = GenericTypeReflector.getTypeParameter(
                compositeClass.getGenericSuperclass(),
                Composite.class.getTypeParameters()[0]);
        if (type instanceof Class || type instanceof ParameterizedType) {
            return GenericTypeReflector.erase(type).asSubclass(Component.class);
        }
        throw new IllegalStateException(getExceptionMessage(type));
    }

    /*
     * copied from Composite#getExceptionMessage(Type)
     */
    private static String getExceptionMessage(Type type) {
        if (type == null) {
            return "Composite is used as raw type: either add type information or override initContent().";
        }

        if (type instanceof TypeVariable) {
            return String.format(
                    "Could not determine the composite content type for TypeVariable '%s'. "
                            + "Either specify exact type or override initContent().",
                    type.getTypeName());
        }
        return String.format(
                "Could not determine the composite content type for %s. Override initContent().",
                type.getTypeName());
    }
}

It would be nice to have this work out of the box.

Legioth commented 4 years ago

I see two ways of solving this.

One would be to include something like your SpringComposite in the Spring integration. The drawbacks here are that you'd need to do something special when using Spring and that the same implementation would have to be separately replicated for CDI as well.

The other alternative would be to make the core Composite class use Instantiator instead of ReflectTools for creating the instance. This would mean that it would automatically work for both Spring and CDI users and that you wouldn't even have to inject the composite instance. The drawbacks would be that Composite would then only be possible to instantiate when VaadinService.getCurrent() is defined and that there's a possibility of surprises for existing applications if old composites would suddenly start behaving as Spring/CDI beans.

F43nd1r commented 4 years ago

I would personally prefer the second option, but any built-in solution would be appreciated.