У меня есть API, который я превращаюсь во внутренний DSL. По сути, большинство методов в моем PoJos возвращает ссылку на это так, чтобы я мог объединить в цепочку методы, вместе декларативно как таковые (синтаксический сахар).
myComponent
.setID("MyId")
.setProperty("One")
.setProperty2("Two")
.setAssociation(anotherComponent)
.execute();
Мой API не зависит от Spring, но я хочу сделать его 'благоприятным для Spring', будучи PoJo, дружественным по отношению к нулевым конструкторам аргумента, методам считывания и методам set. Проблема состоит в том, что Spring, кажется, не обнаруживает мои методы установщика, когда у меня есть непустой тип возврата.
Тип возврата этого очень удобен при объединении в цепочку вместе моих команд, таким образом, я не хочу уничтожать свой программный API просто быть к совместимому с инжекцией Spring.
Существует ли установка в Spring, чтобы позволить мне использовать непустые методы set?
Chris
Спасибо всем (и особенно Эспену, который приложил много усилий, чтобы показать мне различные варианты в 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 должен иметь дело с плавными сеттерами изначально, они никому не причиняют вреда, и он должен просто игнорировать возвращаемое значение.
Требование, чтобы сеттеры были жестко недействительными, это вынудило меня написать намного больше стандартного кода, чем мне потребовалось бы в противном случае. Я ценю спецификацию bean-компонентов, но разрешение bean-компонентов тривиально с использованием отражения даже без использования стандартного преобразователя bean-компонентов, поэтому Spring должен предложить вариант своего собственного преобразователя bean-компонентов, который справится с этими ситуациями.
В любом случае оставьте стандартный механизм по умолчанию, но предложите вариант конфигурации в одну строку. Я с нетерпением жду будущих версий, в которых это может быть при желании смягчено.
Насколько я знаю, простого переключателя нет. Spring использует соглашение Beans и ожидает установщика void. Spring работает с bean-компонентами на уровне свойств через экземпляр интерфейса BeanWrapper . Реализация по умолчанию, BeanWrapperImpl, использует интроспекцию, но вы можете создать свою собственную модифицированную версию, которая использует отражение для поиска методов, соответствующих вашему шаблону.
РЕДАКТИРОВАТЬ: Глядя на код Spring, BeanWrapperImpl
жестко встроен в фабрики компонентов, нет простого способа заменить это другой реализацией. Однако, поскольку Spring использует интроспекцию, мы можем работать над получением java.beans.Introspector для получения желаемых результатов. Вот альтернативы в порядке уменьшения боли:
BeanInfo
для каждого из ваших bean-компонентов. BeanInfo
в интроспектор. Первые два варианта, вероятно, не совсем подходят для вас, так как они включают в себя довольно много изменений. Рассмотрим третий вариант более подробно:
Чтобы узнать, какие bean-компоненты создаются Spring, реализуйте свой собственный BeanFactoryPostProcessor . Это позволяет увидеть все определения bean-компонентов до того, как они будут использованы BeanFactory. Ваша реализация выполняет итерацию по всем BeanDefinitions в факторе и извлекает класс компонента из каждого определения. Теперь вы знаете все используемые классы.
Имея список классов, вы можете приступить к созданию собственного BeanInfos для этих классов. Вы используете Introspector для генерации BeanInfo по умолчанию для каждого класса, который предоставит вам свойства только для чтения для ваших свойств с установщиками возвращаемого значения. Затем вы создаете новый BeanInfo на основе оригинала, но с дескрипторами PropertyDescriptors, ссылающимися на методы установки - ваши установщики возвращаемого значения.
При создании нового beanInfos для каждого класса необходимо убедиться, что Introspector возвращает их при запросе beaninfo для вашего класса. У интроспектора есть собственная карта, которая используется для кеширования beanInfos. Вы можете получить это через отражение, разрешить доступ - setAccessible (true) - и добавить к нему свои экземпляры BeanInfo - map.put (Class, BeanInfo)
.
Когда spring запрашивает у интроспектора BeanInfo для вашего класса bean-компонента, интроспектор возвращает ваш измененный beanInfo вместе с методами установки, сопоставленными с вашими установщиками с возвращаемыми значениями.
Есть ли в Spring настройка, позволяющая мне использовать сеттеры, не являющиеся недействительными?
Простой ответ - нет - такой настройки нет.
Spring разработан для совместимости со спецификацией JavaBeans и требует, чтобы установщики возвращали void
.
Для обсуждения обратитесь к этой ветке весенних форумов . Есть возможные способы обойти это ограничение, упомянутое на форуме, но нет простого решения, и я не думаю, что кто-то действительно сообщил, что они пробовали это и что это сработало.
Как говорили другие, вы рискуете потерять не только дружелюбие к 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
.
Одно простое предложение: обычно используются не сеттеры, а сами имена свойств. Итак, у вас есть сеттер и другой метод для построителя:
component.id("MyId")
.property("One")
.property2("Two")
.association(anotherComponent)
.execute();
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. В данном случае с помощью
элементов.