Как сделать Spring приемлемыми (непустые) сеттеры?

У меня есть API, который я превращаю во внутреннюю DSL. Таким образом, большинство методов в моем PoJos возвращают ссылку на это, так что я могу объединить методы вместе декларативно как таковые (синтаксический сахар).

myComponent
    .setID("MyId")
    .setProperty("One")
    .setProperty2("Two")
    .setAssociation(anotherComponent)
    .execute();

Мой API не зависит от Spring, но я хочу сделать его Spring -Friendly ', будучи дружелюбным к PoJo с конструкторами нулевого аргумента, getters и seters. Проблема в том, что Spring, кажется, не обнаруживает мои методы setter, когда у меня есть невоидный возвращаемый тип.

Возвратный тип этого очень удобен при объединении моих команд, поэтому я не хочу уничтожать свой программный API только для совместимости с Spring injection.

Есть ли параметр в Spring, чтобы я мог использовать не-void сеттеры?

Крис

Ответ 1

Спасибо всем (и особенно Espen, которые приложили много усилий, чтобы показать мне различные варианты в Spring).

В конце концов, я нашел решение, которое не требует конфигурации Spring.

Я следил за ссылкой от Stephen C, а затем нашел ссылку на класс SimpleBeanInfo в этом наборе Threads. Этот класс позволяет пользователю написать собственный код разрешения метода bean, поместив другой класс в тот же пакет, что и класс с нестандартными сеттерами/геттерами, чтобы переопределить логику с помощью "BeanInfo", добавленной к имени класса, и реализации Интерфейс BeanInfo.

Затем я выполнил поиск в Google и нашел этот blog, который указал путь. Решение в блоге было довольно простым, поэтому я применил его для своих целей.

за класс (с плавными сеттерами)

public class MyComponentBeanInfo<T> extends SimpleBeanInfo {

private final static Class<?> _clazz = MyComponent.class;
PropertyDescriptor[] _properties = null;

public synchronized PropertyDescriptor[] getPropertyDescriptors() {
    if (_properties == null) {
        _properties = Helpers.getPropertyDescriptionsIncludingFluentSetters(_clazz);
    }
    return _properties;
}

public BeanDescriptor getBeanDescriptor() {
    return new BeanDescriptor(_clazz);
}
}

Метод генерации PropertyDescriptor

public static PropertyDescriptor[] getPropertyDescriptionsIncludingFluentSetters( Class<?> clazz) {
    Map<String,Method> getterMethodMap = new HashMap<String,Method>();
    Map<String,Method> setterMethodMap = new HashMap<String,Method>();
    Set<String> allProperties = new HashSet<String>();
    PropertyDescriptor[] properties = null;
    try {
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            String name = m.getName();
            boolean isSetter = m.getParameterTypes().length == 1 && name.length() > 3 && name.substring(0,3).equals("set") && name.charAt(3) >= 'A' && name.charAt(3) <= 'Z';
            boolean isGetter = (!isSetter) && m.getParameterTypes().length == 0 && name.length() > 3 && name.substring(0,3).equals("get") && name.charAt(3) >= 'A' && name.charAt(3) <= 'Z';

            if (isSetter || isGetter) {
                name = name.substring(3);
                name = name.length() > 1
                        ? name.substring(0,1).toLowerCase() + name.substring(1)
                        : name.toLowerCase();

                if (isSetter) {
                    setterMethodMap.put(name, m);
                } else {
                    getterMethodMap.put(name, m);
                }
                allProperties.add(name);
            }
        }

        properties = new PropertyDescriptor[allProperties.size()];
        Iterator<String> iterator = allProperties.iterator();
        for (int i=0; i < allProperties.size(); i++) {
            String propertyName = iterator.next();
            Method readMethod = getterMethodMap.get(propertyName);
            Method writeMethod = setterMethodMap.get(propertyName);
            properties[i] = new PropertyDescriptor(propertyName, readMethod, writeMethod);
        }
    } catch (IntrospectionException e) {
        throw new RuntimeException(e.toString(), e);
    }
    return properties;
}

Преимущества этого подхода:

  • Нестандартная конфигурация Spring (Spring не знает о нестандартных сеттерах и видит их как обычно). Никакой зависимости от любых файлов Spring.jar, но доступных из Spring.
  • Кажется, что работает.

Недостатки этого подхода:

  • Мне нужно создать класс BeanInfo для всех моих классов API с нестандартными сеттерами. К счастью, существует только около 10 таких классов, и, перемещая логику разрешения метода в отдельный класс, у меня есть только одно место для поддержки.

Заключительные мысли

По-моему, Spring должен иметь дело с плавными сеттерами изначально, они никому не повредят, и он должен просто игнорировать возвращаемое значение.

Требовав, чтобы сеттеры были жестко недействительными, он заставил меня написать намного больше кода котельной плиты, чем мне было бы нужно в противном случае. Я ценю спецификацию bean, но разрешение bean тривиально с использованием отражения без использования стандартного распознавателя bean, поэтому Spring должен предлагать вариант своего собственного распознавателя bean, который будет обрабатывать эти ситуации.

В любом случае оставьте стандартный механизм по умолчанию, но предложите вариант конфигурации с одной строкой. Я с нетерпением жду будущих версий, где это может быть необязательно расслаблено.

Ответ 2

Есть ли параметр в Spring, чтобы я мог использовать не-void сеттеры?

Простой ответ: Нет - такой установки нет.

Spring предназначен для совместимости с спецификацией JavaBeans и требует, чтобы сеттеры возвращали void.

Для обсуждения см. этот Spring форум. На этом форуме есть возможные способы ограничения этого ограничения, но нет простого решения, и я не думаю, что кто-то действительно сообщал, что они пробовали это и что он сработал.

Ответ 3

Spring также можно настроить с помощью конфигурации Java.

Пример:

@Configuration
public class Config {
    @Bean
    public MyComponent myComponent() {
        return MyComponent
            .setID(id)
            .setProperty("One", "1")
            .setProperty("Two", "2")
            .setAssociation(anotherConfig.anotherComponent())
            .execute();
    }

    @Autowired
    private AnotherConfig anotherConfig;

    @Value("${id}")
    private String id;
}

У вас есть хороший неизменный объект. Фактически вы реализовали шаблон Builder!

Обновлено для ответа на комментарий Криса:

Я предполагаю, что это не совсем то, что вы хотите, но использование файлов свойств решает некоторые проблемы. См. Поле id в приведенном выше примере.

В противном случае вы можете использовать Spring FactoryBean pattern:

public class MyComponentFactory implements FactoryBean<MyComponent> {

    private MyComponent myComponent;

    public MyComponentFactory(String id, Property propertyOne, ..) {
        myComponent = MyComponent
            .setID(id)
            .setProperty("One", "1")
            .set(..)
            .execute();
    }

    public MyComponent getObject() throws Exception {
        return myComponent;
    }

    public Class<MyComponent> getObjectType() {
        return MyComponent.class;
    }

    public boolean isSingleton() {
        return false;
    }
}

В FactoryBean вы защищаете конфигурацию от объекта, возвращаемого методом getObject().

В конфигурации XML вы настраиваете реализацию FactoryBean. В этом случае с элементами <constructor-arg />.

Ответ 4

Одно простое предложение, обычно не использовать сеттеры, но имена свойств сами. Так что у вас есть сеттер и есть другой метод для строителя:

component.id("MyId")
    .property("One")
    .property2("Two")
    .association(anotherComponent)
    .execute();

Ответ 5

Насколько я знаю, простого переключателя нет. Spring использует соглашение Beans и ожидает установщика пустот. Spring работает с Beans на уровне свойства через экземпляр Интерфейс BeanWrapper. Реализация по умолчанию, BeanWrapperImpl, использует интроспекцию, но вы можете создать свою собственную модифицированную версию, которая использует отражение, чтобы найти методы, соответствующие вашему шаблону.

РЕДАКТИРОВАТЬ: Если посмотреть на код Spring, BeanWrapperImpl жестко подключен к фабрикам bean, нет простого способа заменить его другой реализацией. Однако, поскольку Spring использует интроспекцию, мы можем работать над получением java.beans.Introspector для получения желаемых результатов. Вот альтернативы в порядке уменьшения боли:

  • измените подпись метода на ваших сеттерах.
  • реализуйте свои собственные классы BeanInfo для каждого из Beans
  • Используйте отражение, чтобы подключить динамически генерируемые классы BeanInfo к интроспектору.

Первые два варианта, вероятно, не очень подходят для вас, поскольку они включают в себя довольно много изменений. Изучение третьего варианта более подробно:

  • Чтобы узнать, какой Beans создается spring, реализуйте свой собственный BeanFactoryPostProcessor. Это позволит увидеть все определения bean до того, как они будут использованы BeanFactory. Ваша реализация перебирает все параметры BeanDefinitions в коэффициенте и извлекает класс bean из каждого определения. Теперь вы знаете все используемые классы.

  • Со списком классов вы можете настроить создание своих BeanInfos для этих классов. Вы используете Introspector для генерации по умолчанию BeanInfo по каждому классу, который даст вам свойства только для чтения для ваших свойств с установками возвращаемых значений. Затем вы создаете новую BeanInfo, основанную на оригинале, но с PropertyDescriptors, ссылающейся на методы setter - ваши настройки возвращаемого значения.

  • С новым beanInfos, сгенерированным для каждого класса, вы должны убедиться, что Introspector возвращает их при запросе beaninfo для вашего класса. Интроспектор имеет закрытую карту, которая используется для кэширования beanInfos. Вы можете получить это через отражение, включить доступ - setAccessible (true) - и добавить к нему свои экземпляры BeanInfo - map.put(Class,BeanInfo).

  • Когда Spring запрашивает Introspector для BeanInfo для вашего класса bean, интроспектор возвращает ваш измененный beanInfo, в комплекте с методами setter, сопоставленными вашим сеттерам с возвращаемыми значениями.

Ответ 6

Как говорили другие, это не просто Spring дружелюбие, которое вы рискуете потерять. Не-void setter на самом деле не является сеттерным для JavaBeans, и всевозможные другие инструменты (валидаторы, маршаллеры, зрители, скобки, все, что вы можете придумать), вероятно, будут использовать Introspector и BeanInfo, которые ожидают, что настройки будут пустыми.

С учетом этого, насколько гибким является требование, чтобы они назывались setX? Много свободного интерфейса в Java используют withX. Если вы используете Eclipse, вы можете создать шаблон генерации кода, чтобы сделать X getX(), void setX(X x) и X withX(X x) для вас. Если вы используете какой-либо другой инструмент codegen, я могу себе представить, что добавление методов withX с четким сеттер/геттером также будет легким.

Слово with кажется немного странным, но когда вы видите его рядом с конструктором, он отлично читается.

Request r = new Request().withEndpoint("example.com")
                         .withPort(80)
                         .withSSL(false)
                         .withFoo("My Foo");

service.send(r);

Одним из таких API является AWS SDK для Java, с которым вы можете ознакомиться на примерах. Предостережение, отличное от темы, состоит в том, что boolean getters можно назвать isX, но boolean getters должны быть вызваны getX.