2010-05-24 2 views
14

저는 내부 DSL로 바뀌고있는 API를 가지고 있습니다. 이와 같이, PoJos의 대부분의 메소드는 이것에 대한 참조를 반환하므로 선언적으로 메소드를 체인화 할 수 있습니다 (구문 설탕).Spring이 유창한 (비 void) setter를 허용하는 방법?

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

내 API는 봄에 의존하지 않는하지만 난 제로 인수 생성자, getter 및 setter 친화적 인 POJO 인하여 '봄 친화적'하게하고 싶습니다. 문제는 비 void 반환 유형이있을 때 Spring이 내 setter 메서드를 감지하지 못하는 것입니다.

반환 형식은 내 명령을 연결할 때 매우 편리하므로 프로그래밍 방식의 API를 파괴하고 싶지는 않습니다. 단지 스프링 주입과 호환되어야합니다.

비 void setter를 사용할 수 있도록 Spring에 설정이 있습니까?

크리스

+0

사실 그것은 다소 흥미로운 아이디어입니다. – alternative

답변

9

감사합니다 모두 (그리고 특히 봄에 다양한 옵션을 표시하기 위해 많은 노력을 기울인 Espen).

결국 스프링 구성을 필요로하지 않는 솔루션을 직접 발견했습니다.

Stephen C의 링크를 따라 가서 해당 스레드 내에서 SimpleBeanInfo 클래스에 대한 참조를 찾았습니다. 이 클래스는 사용자가 비표준 setter/getter가있는 클래스와 동일한 패키지에 다른 클래스를 배치하여 클래스 이름에 'BeanInfo'가 추가 된 로직을 무시하고 'BeanInfo'를 구현함으로써 자신의 bean 메소드 해결 코드를 작성할 수있게합니다. '인터페이스.

그런 다음 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; 
} 

장점 :

  • 커스텀 스프링 설정이 없다. (스프링은 비표준 setter를 인식하지 못하고 정상적인 것으로 간주한다.) Spring .jar 파일에는 의존하지 않지만 Spring에서는 액세스 할 수 있습니다.
  • 그냥 작동하는 것 같습니다. 이 방법

단점 :

  • I 표준이 아닌 세터 내 API 클래스 모두를위한의 BeanInfo 클래스를 만들 배치해야합니다. 다행스럽게도 약 10 개의 클래스가 있으며 메소드 분석 로직을 별도의 클래스로 옮겨서 유지할 수있는 곳이 하나뿐입니다.

제 생각에는 생각

닫기, 봄은 기본적으로, 그들은 사람을 다치게하지 않아 유창 세터를 처리해야하며, 그것은 단지 반환 값을 무시한다.

세터가 엄격하게 무효가되도록 요구함으로써 보일러 플레이트 코드를 다른 방법으로 많이 쓰게되었습니다. Bean Specification을 고맙게 생각하지만 Bean 해상도는 표준 Bean 리졸버를 사용하지 않고도 리플렉션을 사용하는 것이 쉽지 않으므로 Spring은이 상황을 처리 할 Bean 리졸버 옵션을 제공해야한다.

반드시 기본 메커니즘을 그대로두고 한 줄 구성 옵션을 제공하십시오. 이 버전이 선택적으로 완화 될 수 있기를 기대합니다.

+0

이것은 솔루션의 첫 번째 단점이라는 것을 언급해야합니다 ('is'와 'boolean getter'를 조사 할 필요가 있습니다). 나는 내가 더 발전함에 따라 갱신 할 것이다. – Chris

+0

+1 : 아주 흥미로운 대안! 그러나 코드에서 Spring을 완전히 피하기 위해 복잡도가 높습니다. – Espen

+0

N.B. Spring은 3.1 이후로 "유창한"setter를 허용한다. (메소드가 올바른 이름과 매개 변수를 가지면 아무 것도 반환 할 수 없다.) –

9

나 비 무효 세터를 사용할 수 있도록 봄의 설정이 있습니까?

간단한 대답은 아니오입니다. 그런 설정이 없습니다.

스프링은 JavaBeans 사양과 호환되도록 설계되었으며 setter는 void을 반환해야합니다.

자세한 내용은 this Spring Forums thread을 참조하십시오. 포럼에 언급 된 이러한 한계를 극복 할 수있는 방법이 있지만 간단한 해결책은 없으며 아무도 실제로이 문제를 시도하고 효과가 있다고보고하지 않았습니다.

+0

포럼에 대한 링크 주셔서 감사합니다. 운 좋게도 대답은 어렵지 않습니다. BeanConfig 인터페이스 구현과 같은 해킹으로 간주되지 않고 Java Introspector 클래스 자체의 한계를 극복하는 방법과 수단이 있습니다. – Chris

7

봄도 Java configuration으로 구성 할 수 있습니다.

예 :

@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 필드를 참조하십시오. 개체가 getObject() 메서드에서 반환에서 당신은 구성을 보호의의 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; 
    } 
} 

:

그 밖에, 당신은 Spring의 FactoryBean 패턴을 사용할 수 있습니다.

XML 구성에서 FactoryBean 구현을 구성합니다. 이 경우 <constructor-arg /> 요소가 있습니다.

+0

감사합니다. 그렇지만 Spring 사용자에게 Java 구성 접근법을 사용하도록 강요하고 있으며 XML 구성 접근법과의 호환성을 잃어 버릴 것 같습니다. 필자는 문서화 된 것처럼 Java 접근 방식을 선호하지만 사용자를 대신하여 그러한 선택을하고 싶지 않습니다. – Chris

+0

다시 한번 감사 드리지만, Spring이 내포물을 주입하기위한 많은 방법 중 하나 일 뿐이므로, Spring은 내 제품에서 Spring을 참조하지 않고 작동하도록하는 것이 좋습니다. 나는 이제 내 자신의 문제에 대한 해결책을 발견했다. 나는 곧 게시 할 것이다. – Chris

3

내가 아는 한 간단한 전환이 없습니다. Spring은 Beans 규칙을 사용하며 void setter를 기대합니다. Spring은 BeanWrapper 인터페이스의 인스턴스를 통해 속성 레벨에서 빈들과 동작한다. 기본 구현 인 BeanWrapperImpl은 인트로 스펙 션을 사용하지만 리플렉션을 사용하여 패턴과 일치하는 메소드를 찾는 자체 수정 버전을 만들 수 있습니다.

EDIT : 스프링 코드를 보면 BeanWrapperImpl은 빈 공장에 하드 와이어되어 있기 때문에 이것을 다른 구현으로 바꾸는 간단한 방법은 없습니다. 그러나 봄은 인트로 스펙 션을 사용하므로 java.beans.Introspector을 사용하여 원하는 결과를 얻을 수 있습니다. 다음은 통증 감소 순서입니다.

  1. 3 세터의 방법 서명을 준수하도록 변경하십시오.
  2. 각 콩에 대해 클래스를 구현하십시오.
  3. 동적으로 생성 된 BeanInfo 클래스를 리플렉션을 사용하여 인트로 스펙트에 연결하십시오.

처음 두 옵션은 변경 사항이 많으므로 실제로 옵션이 아닙니다. 더 구체적으로 세 번째 옵션을 탐색 :

  1. 자신의 BeanFactoryPostProcessor을 구현, 콩은 스프링에 의해 인스턴스화되고있는 알고하십시오. 이것은 BeanFactory에 의해 사용되기 전에 모든 bean 정의를 보게된다. 구현은 팩터의 모든 BeanDefinition에 대해 반복 수행하고 각 정의에서 Bean 클래스를 가져옵니다. 이제 사용중인 모든 클래스를 알 수 있습니다.

  2. 클래스 목록을 사용하면 이러한 클래스에 대한 고유 한 BeanInfos를 만들 수 있습니다. Introspector를 사용하여 각 클래스에 대한 기본 BeanInfo를 생성합니다. 반환 값 설정자로 속성에 대한 읽기 전용 속성을 제공합니다. 그런 다음 원본을 기반으로하지만 Setter 메서드를 참조하는 PropertyDescriptors (반환 값 설정자)를 기반으로 새 BeanInfo를 만듭니다.

  3. 각 클래스에 대해 생성 된 새 beanInfos를 사용하면 Introspector가 해당 클래스의 beaninfo를 묻는 메시지를 표시 할 때 Introspector가이를 반환하는지 확인해야합니다. Introspector에는 beanInfos를 캐시하는 데 사용되는 개인용 맵이 있습니다. 리플렉션을 통해 이것을 잡아서 access-setAccessible (true)를 활성화하고 BeanInfo 인스턴스를 map.put(Class,BeanInfo)에 추가 할 수 있습니다.

  4. 봄이 Introspector에 BeanInfo에 대한 Bean 클래스를 요청하면 Introspector는 반환 된 값으로 설정 메소드에 매핑 된 setter 메소드로 완료된 수정 된 beanInfo를 리턴합니다.

+0

자세한 분석을 보내 주셔서 감사합니다. 매우 이해하기 쉽습니다. 내 솔루션은 귀하의 # 2 제안 솔루션입니다 (나는 당신이 편집 한대로 이것을 쓰고있었습니다). # 3은 흥미로운 해결책이지만 Spring 보일러 플레이트를 작성해야합니다 (스프링 특정 코드의 한 줄을 쓰지 않거나 실패했습니다). 필자는 Ant 플러그인을 작성하여 관련 클래스에 대한 BeanInfo 객체를 자동으로 내보낼 수 있다고 생각합니다. 이것은 소스에 포함되지만 컴파일되지는 않습니다. – Chris

+0

beaninfo를 생성하기를 원하는 클래스를 찾기위한 # 3의 보일러 플레이트가 없습니다. (이것을 건너 뛰고 컴파일 타임에 클래스 목록을 생성 할 수 있지만 런타임에는 BeanInfos를 생성합니다.) 기본적으로 사용자가 채택했지만 실행 시간보다는 컴파일 시간에 완료된 솔루션입니다. 개인적으로, 나는 runttime에서 beaninfos를 생성하는 편이 낫다. - 실행/테스트/컴파일을 더 빠르게하고, 디버깅 중에 핫 리로딩을 가능하게하지만, 물론 상황을 가장 잘 알기 때문에 호출하는 것이 좋다. – mdma

4

한 가지 간단한 제안으로 세터를 사용하지 않는 것이 일반적이지만 속성 자체에 이름이 지정됩니다. 그래서 세터가 있고, 빌더를위한 또 다른 방법이 있습니다

다른 사람이 말했듯이
component.id("MyId") 
    .property("One") 
    .property2("Two") 
    .association(anotherComponent) 
    .execute(); 
2

, 당신이 잃을 위험이 단지 봄 편의 아니다 있습니다. 비 void setter는 JavaBeans에 관한 한 실제로 setter가 아니며, 다른 모든 도구 (유효성 검사기, 마샬 러, 뷰어, 영속화 자 등)는 아마도 IntrospectorBeanInfo을 사용할 것이며 setters null이되도록 (듯이)합니다.

이러한 점을 염두에두고 setX이라는 요구 사항이 얼마나 유연합니까? Java의 유창한 인터페이스는 많게는 withX을 사용합니다. Eclipse를 사용하는 경우 코드 생성 템플릿을 만들어 X getX(), void setX(X x)X withX(X x)으로 만들 수 있습니다. 다른 codegen 도구를 사용하고 있다면 withX 유창한 setter/getter 메소드를 추가하는 것이 쉬울 것이라고 생각합니다.

단어가 다소 이상하게 보입니다. 그러나 생성자 옆에 단어가 표시되면 정말 잘 읽습니다.

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

service.send(r); 

하나는 이러한 API는 예제를 참조 할 수있는 AWS SDK for Java이다. 주제 밖의 경고는 boolean 게터는 isX이라고 할 수 있지만 Boolean 게터는 getX이라고해야한다는 것입니다.

관련 문제