Как заставить Spring принимать свободно (не пустые) сеттеры?

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

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

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

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

есть ли настройка весной, чтобы позволить мне использовать непустые сеттеры?

Крис

6 ответов


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

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

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

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

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

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

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

Закрытие Мысли

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

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

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


есть ли настройка весной, чтобы позволить мне использовать непустые сеттеры?

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

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

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


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

Else, вы можете использовать весну 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 /> элементы.


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

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

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

EDIT: глядя на код Spring,BeanWrapperImpl жестко подключен к фабрикам фасоли, нет простого способа заменить это другой реализацией. Однако, поскольку spring использует интроспекцию, мы можем работать над получением java.зернышки.Интроспектор!--6--> произвести результаты мы хотим. Вот варианты в порядке уменьшения боли:

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

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

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

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

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

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


как говорили другие, вы рискуете потерять не только весеннее дружелюбие. Не-пустотный сеттер на самом деле не сеттер, насколько это касается JavaBeans, и все виды других инструментов (валидаторы, маршаллеры, зрители, персистенты, все остальное, что вы можете придумать), вероятно, будут использовать самоанализа и beaninfo, которые, которые ожидают, что сеттеры будут null.

имея это в виду, насколько гибким является требование, чтобы они назывались setX? Много свободно интерфейсы в Java use 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.