Как заставить Spring принимать беглые (не пустые) сеттеры? - PullRequest
14 голосов
/ 25 мая 2010

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

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

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

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

Есть ли в Spring параметр, позволяющий мне использовать не пустые сеттеры?

Chris

Ответы [ 6 ]

9 голосов
/ 25 мая 2010

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

В итоге я сам нашел решение, которое не требует настройки Spring.

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

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

на класс (с установщиками текучей среды)

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 .jar, но доступных из Spring.
  • Просто, кажется, работает.

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

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

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

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

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

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

9 голосов
/ 25 мая 2010

Есть ли в Spring параметр, позволяющий мне использовать не пустые сеттеры?

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

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

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

7 голосов
/ 25 мая 2010

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 в приведенном выше примере.

Иначе, вы можете использовать шаблон SpringB FactoryBean :

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 голосов
/ 25 мая 2010

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

component.id("MyId")
    .property("One")
    .property2("Two")
    .association(anotherComponent)
    .execute();
3 голосов
/ 25 мая 2010

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

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

  1. изменить подпись метода на ваших установщиках для соответствия.
  2. реализовать свои собственные BeanInfo классы для каждого из ваших компонентов
  3. Используйте рефлексию для включения динамически генерируемых BeanInfo классов в интроспектор.

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

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

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

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

  4. Когда Spring запрашивает Introspector для BeanInfo для вашего класса EJB, Introspector возвращает ваше модифицированное beanInfo вместе с методами сеттера, сопоставленными с вашими сеттерами с возвращаемыми значениями.

2 голосов
/ 25 мая 2010

Как уже говорили другие, вы рискуете потерять не только дружелюбие к весне. Установщик non-void на самом деле не является установщиком в том, что касается 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 геттеры могут называться isX, но Boolean геттеры должны называться getX.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...