Как сделать пружину, принимают быстрые (непустые) методы set?

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

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

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

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

Существует ли установка в Spring, чтобы позволить мне использовать непустые методы set?

Chris

14
задан jasonmp85 28 May 2010 в 08:49
поделиться

6 ответов

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

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

Я перешел по ссылке от Stephen C, а затем нашел ссылку на класс SimpleBeanInfo в этом наборе потоков. Этот класс позволяет пользователю написать свой собственный код разрешения метода компонента, поместив другой класс в тот же пакет, что и класс с нестандартными сеттерами / получателями, чтобы переопределить логику с добавлением 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 должен иметь дело с плавными сеттерами изначально, они никому не причиняют вреда, и он должен просто игнорировать возвращаемое значение.

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

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

9
ответ дан 1 December 2019 в 07:51
поделиться

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

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

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

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

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

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

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

  4. Когда spring запрашивает у интроспектора BeanInfo для вашего класса bean-компонента, интроспектор возвращает ваш измененный beanInfo вместе с методами установки, сопоставленными с вашими установщиками с возвращаемыми значениями.

3
ответ дан 1 December 2019 в 07:51
поделиться

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

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

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

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

9
ответ дан 1 December 2019 в 07:51
поделиться

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

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

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

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

service.send(r);

Одним из таких API является AWS SDK для Java , примеры которого можно найти. Не по теме предостережение: логические геттеры могут называться isX , но логические геттеры должны называться getX .

2
ответ дан 1 December 2019 в 07:51
поделиться

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

component.id("MyId")
    .property("One")
    .property2("Two")
    .association(anotherComponent)
    .execute();
4
ответ дан 1 December 2019 в 07:51
поделиться

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

Кроме того, вы можете использовать FactoryBean от Spring:

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. В данном случае с помощью элементов.

7
ответ дан 1 December 2019 в 07:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: