Spring, aspekty, rzutowanie beana

0

Klasa main:

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/springinaction/springidol/spring-idol.xml");

        Instrumentalist performer  = (Instrumentalist) context.getBean("eddie");
        try {
            performer.perform();
        } catch (PerformanceException e) {
            e.printStackTrace();
        }
    }
}

Interfejs Performer:

public interface Performer {
  void perform() throws PerformanceException;
}

Interfejs Instrument:

public interface Instrument {
  public void play();
}

Klasa Instrumentalist:

public class Instrumentalist implements Performer {
  public void perform() throws PerformanceException {
    instrument.play();
  }

  private Instrument instrument;

  public void setInstrument(Instrument instrument) {
    this.instrument = instrument;
  }

  public Instrument getInstrument() {
    return instrument;
  }
}

Klasa Gitara:

public class Guitar implements Instrument {
  public void play() {
    System.out.println("Strum strum strum");
  }
}
  <bean id="eddie"
      class="com.springinaction.springidol.Instrumentalist">
    <property ref="gitara" name="instrument"/>
  </bean>

    <bean id="gitara" class="com.springinaction.springidol.Guitar" />

I taki kod działa.

Dodaję sobie klasę Audience:

public class Audience {
  public void takeSeats() { 
    System.out.println("The audience is taking their seats.");
  }

  public void turnOffCellPhones() { 
    System.out.println("The audience is turning off their cellphones");
  }

  public void applaud() { 
    System.out.println("CLAP CLAP CLAP CLAP CLAP");
  }

  public void demandRefund() { 
    System.out.println("Boo! We want our money back!");
  }
}

i dodaję do configuracji springa aspekty:

<aop:config>
  <aop:aspect ref="audience"><!--<co id="co_refAudienceBean"/>-->

    <aop:before pointcut=
         "execution(* com.springinaction.springidol.Performer.perform(..))"
      method="takeSeats" /> <!--<co id="co_beforePointcut"/>-->

    <aop:before pointcut=
         "execution(* com.springinaction.springidol.Performer.perform(..))"
      method="turnOffCellPhones" /> <!--<co id="co_beforePointcut2"/>-->

    <aop:after-returning pointcut=
         "execution(* com.springinaction.springidol.Performer.perform(..))"
      method="applaud" /> <!--<co id="co_afterPointcut"/>-->

    <aop:after-throwing pointcut=
         "execution(* com.springinaction.springidol.Performer.perform(..))"
      method="demandRefund" /> <!--<co id="co_afterThrowingPointcut"/>-->

 </aop:aspect>
</aop:config>

I aplikacja się wywala:

INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@61b4607f: defining beans [eddie,gitara,audience,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.aop.aspectj.AspectJPointcutAdvisor#0,org.springframework.aop.aspectj.AspectJPointcutAdvisor#1,org.springframework.aop.aspectj.AspectJPointcutAdvisor#2,org.springframework.aop.aspectj.AspectJPointcutAdvisor#3]; root of factory hierarchy
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.springinaction.springidol.Instrumentalist
	at com.springinaction.springidol.Main.main(Main.java:13)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:601)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

Jeśli zamienię na taką linikę w Main:

Performer performer  = (Performer) context.getBean("eddie");

to działa. Czy mógłby to ktoś wyjaśnić?

2
  1. Czemu nie uzywasz adnotacji tylko piszesz jak w Springu 2.X?
  2. Problem polega na tym, że aspekty to nie jest żadna "magia". Aspekty działają tak, że Spring tworzy sobie obiekt "proxy" który ma w sobie referencje do twojego prawdziwego obiektu i np. dla before robi
public void pointcutowaMetoda(){
    twojeInstrukcjeBefore();
    prawdziwyObiekt.pointcutowaMeotda();
}

Jak widać taki obiekt proxy musi imitować interfejs klasy którą obsługuje. I tak też standardowo robi (tzn jest klasą która implementuje dany interfejs). Problem w tym że ty próbujesz go przypisać do referencji na konkretną klasę a nie interfejs a tego zrobić już nie wolno. Załóżmy że robisz AOP dla klasy ArrayList. To wygenerowane AOP wygląda wtedy tak:

class AOPArrayListProxy implements List{}

I przypisać takiego obiektu do ArrayList nie możesz, ale do List już jak najbardziej.

Szerzej ten problem jest opisany tutaj: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-introduction-proxies

Można wymusić na Springu wykorzystanie cgliba do generowania class-proxy zamiast interface-proxy, wtedy wygenerowana klasa będzie wyglądała tak:

class AOPArrayListProxy extends ArrayList{}

i będzie można ją przypisać już do referencji na konkretną klasę.

Generalnie ten problem jest bardziej powszechnie spotykany kiedy używa się np. @Transactional które jest implementowane za pomocą AOP.

0

@Shalom Skoro już poruszyliśmy ten temat.

To masz na myśli, żeby wszystko robić za pomocą adnotacji? Ustawić tylko taki "czysty" xml springowy i wstrzyknięcia czy konfiguracje i skanowanie komponentów "jechać" na adnotacjach?

0

// uwaga protip:
.. A czy wiesz, że jak wpiszesz w google "spring konfiguracja xml vs adnotacje 4programmers" , "spring annotations or xml" , "spring anotations vs xml" to znajdzies to co chcesz
samemu i szybciej i więcej i wgl? Tego się naucz, bo to też ważne!

0

@karolinaa o i mam.

@Shalom napisał:

A ja tam jestem oldschoolowy i uważam że główną konfiguracje w xmlu a reszta adnotacjami w kodzie

1 użytkowników online, w tym zalogowanych: 0, gości: 1