2015-04-23 133 views
1

好日子,我正在使用JavaFX上的Spring AOP進行項目工作,不幸的是,當我嘗試包裝在JavaFX場景中使用的接口時,我收到一個空指針。這是堆棧跟蹤。Spring AOP代理不能用於JavaFX

Exception in Application start method 
java.lang.reflect.InvocationTargetException 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:483) 
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:363) 
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:303) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:483) 
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767) 
Caused by: java.lang.RuntimeException: Exception in Application start method 
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:875) 
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$147(LauncherImpl.java:157) 
    at com.sun.javafx.application.LauncherImpl$$Lambda$48/756185697.run(Unknown Source) 
    at java.lang.Thread.run(Thread.java:745) 
Caused by: java.lang.NullPointerException 
    at javafx.scene.Node.getScene(Node.java:907) 
    at javafx.scene.Scene$9.invalidated(Scene.java:1074) 
    at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:111) 
    at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:145) 
    at javafx.scene.Scene.setRoot(Scene.java:1038) 
    at javafx.scene.Scene.<init>(Scene.java:325) 
    at javafx.scene.Scene.<init>(Scene.java:181) 
    at com.hccs.sample.aspectj.Main.start(Main.java:42) 
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$153(LauncherImpl.java:821) 
    at com.sun.javafx.application.LauncherImpl$$Lambda$51/50630420.run(Unknown Source) 
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$166(PlatformImpl.java:323) 
    at com.sun.javafx.application.PlatformImpl$$Lambda$44/1051754451.run(Unknown Source) 
    at com.sun.javafx.application.PlatformImpl.lambda$null$164(PlatformImpl.java:292) 
    at com.sun.javafx.application.PlatformImpl$$Lambda$47/1570685826.run(Unknown Source) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$165(PlatformImpl.java:291) 
    at com.sun.javafx.application.PlatformImpl$$Lambda$46/1775282465.run(Unknown Source) 
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95) 
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) 
    at com.sun.glass.ui.win.WinApplication.lambda$null$141(WinApplication.java:102) 
    at com.sun.glass.ui.win.WinApplication$$Lambda$37/1109371569.run(Unknown Source) 
    ... 1 more 
Exception running application com.hccs.sample.aspectj.Main 

這裏是Main方法,其中我創建了MyScene接口的實例並用代理包裝它。

public class Main extends Application { 

    private static ApplicationContext context; 

    public static void main(String[] args) { 
     context = new AnnotationConfigApplicationContext(AppConfig.class); 
     Application.launch(Main.class); 
    } 

    @Override 
    public void start(Stage primaryStage) throws Exception { 
     MyScene scene = context.getBean(MyScene.class); 
     if (scene == null) { 
      System.out.println("Scene is null on creation"); 
      System.exit(0); 
     } else { 
      System.out.println("Scene is not null on creation."); 
     } 

     // these code are the source of the problem, when these code are 
     // commented, it works fine. 
     scene = ProxyWrapper.wrap(scene); 
     if (scene == null) { 
      System.out.println("Scene is null on wrapping"); 
      System.exit(0); 
     } else { 
      System.out.println("Scene is not null on wrapping"); 
     } 
     // comment it up to here 

     // No problem with manual invocation of methods 
     scene.eventOne(); 
     scene.eventTwo(); 
     scene.eventThree(); 

     // Where null pointer occurs 
     primaryStage.setScene(new Scene((Parent) scene)); 
     primaryStage.show(); 
    } 
} 

這裏是應用程序配置。

@Configuration 
@ComponentScan(basePackages = { "com.hccs.sample.aspectj" }) 
public class AppConfig { 

} 

這是需要包裝的場景接口和實現。

public interface MyScene { 
    public void initialize(); 

    public void eventOne(); 

    public void eventTwo(); 

    public void eventThree(); 

} 

@Lazy 
@Component 
public class MySceneImpl extends BorderPane implements MyScene { 

    @FXML 
    private Button cmdOne; 
    @FXML 
    private Button cmdTwo; 
    @FXML 
    private Button cmdThree; 

    public MySceneImpl() { 
     try { 
      FXMLLoader loader = new FXMLLoader(getClass().getResource(
        "/com/hccs/sample/aspectj/resources/MyScene.fxml")); 
      loader.setRoot(this); 
      loader.setController(this); 
      loader.load(); 
      System.out.println("\nKARDS!!\n"); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    @FXML 
    @Override 
    public void initialize() { 
    } 

    @FXML 
    @Override 
    public void eventOne() { 
     System.out.println("One"); 
    } 

    @FXML 
    @Override 
    public void eventTwo() { 
     loopToTen(); 
    } 

    @Override 
    public void eventThree() { 
     new Thread() { 
      @Override 
      public void run() { 
       loopToTen(); 
      } 
     }.start(); 
    } 

    private void loopToTen() { 
     for (int c = 0; c < 10; c++) { 
      try { 
       Thread.sleep(100); 
       System.out.println(c + 1); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 

    } 

} 

下面是場景中使用的FXML,

<fx:root type="BorderPane" xmlns:fx="http://javafx.com/fxml/1" 
    xmlns="http://javafx.com/javafx/8"> 
    <center> 
     <HBox alignment="CENTER" spacing="10.0" BorderPane.alignment="CENTER"> 
      <children> 
       <Button fx:id="cmdOne" mnemonicParsing="false" onAction="#eventOne" 
        text="One" /> 
       <Button fx:id="cmdTwo" mnemonicParsing="false" onAction="#eventTwo" 
        text="Two" /> 
       <Button fx:id="cmdThree" mnemonicParsing="false" onAction="#eventThree" 
        text="Three" /> 
      </children> 
     </HBox> 
    </center> 
    <padding> 
     <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> 
    </padding> 
</fx:root> 

這是用於包裝的場景類別。

public class ProxyWrapper { 
    @SuppressWarnings("unchecked") 
    public static <T> T wrap(T scene) { 
     MyPointcut pointcut = new MyPointcut(); 
     MyMethodInterceptor aspect = new MyMethodInterceptor(); 
     Advisor advisor = new DefaultPointcutAdvisor(pointcut, aspect); 
     ProxyFactory pf = new ProxyFactory(); 
     pf.setTarget(scene); 
     pf.addAdvisor(advisor); 
     return (T) pf.getProxy(); 
    } 
} 

這是Pointcut和MethodInterceptor類。

public class MyPointcut extends DynamicMethodMatcherPointcut { 

    @Override 
    public ClassFilter getClassFilter() { 
     return new ClassFilter() { 
      @Override 
      public boolean matches(Class<?> clazz) { 
       return clazz.getName().contains("MyScene"); 
      } 
     }; 
    } 

    @Override 
    public boolean matches(Method method, Class<?> clazz, Object[] key) { 
     System.out.println(method.getName() + " == event " 
       + method.getName().contains("event")); 
     return method.getName().contains("eventT"); 
    } 

} 

@Aspect 
public class MyMethodInterceptor implements MethodInterceptor { 
    @Override 
    public Object invoke(MethodInvocation invocation) throws Throwable { 
     System.out.println("START: " + invocation.getMethod().getName()); 
     Object val = invocation.proceed(); 
     System.out.println("END: " + invocation.getMethod().getName()); 
     return val; 
    } 
} 

最後,這是項目的pom.xml。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 
    <modelVersion>4.0.0</modelVersion> 
    <groupId>com.hccs.sample</groupId> 
    <artifactId>sample-aspectj</artifactId> 
    <version>1.0.0</version> 
    <name>sample-aspectj</name> 

    <dependencies> 
     <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-core</artifactId> 
      <version>4.1.6.RELEASE</version> 
     </dependency> 
     <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-context</artifactId> 
      <version>4.1.6.RELEASE</version> 
     </dependency> 
     <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-aop</artifactId> 
      <version>4.1.6.RELEASE</version> 
     </dependency> 
     <dependency> 
      <groupId>org.aspectj</groupId> 
      <artifactId>aspectjrt</artifactId> 
      <version>1.8.5</version> 
     </dependency> 
     <dependency> 
      <groupId>org.aspectj</groupId> 
      <artifactId>aspectjweaver</artifactId> 
      <version>1.8.5</version> 
     </dependency> 
     <dependency> 
      <groupId>aopalliance</groupId> 
      <artifactId>aopalliance</artifactId> 
      <version>1.0</version> 
     </dependency> 
    </dependencies> 
</project> 

如果有一種方法讓我讓javafx識別spring aop代理,請分享它並幫助我,謝謝!

+0

我類似的問題,但我不能讓它在所有的工作 - 在FXML組件總是空(我用'spring.aop.proxy- target-class = true')。任何幫助? https://stackoverflow.com/questions/45933616/cant-create-aspect-in-spring-boot?noredirect=1#comment78826078_45933616 – Mejmo

回答

1

我不能完全弄清楚爲什麼這會失敗,但使MySceneImplBorderPane的一個子類導致了這個問題。基本上,爲攔截方法而創建的代理不會正確初始化BorderPane實例。

此修復程序是使用聚合而不是繼承。一個方法添加到MyScene

public Parent getView() ; 

,並相應地更新實現類:

@Lazy 
@Component 
public class MySceneImpl implements MyScene { 

    @FXML 
    private Button cmdOne; 
    @FXML 
    private Button cmdTwo; 
    @FXML 
    private Button cmdThree; 

    private final BorderPane view ; 

    public MySceneImpl() { 

     view = new BorderPane(); 

     try { 
      FXMLLoader loader = new FXMLLoader(getClass().getResource(
        "/application/MyScene.fxml")); 
      loader.setRoot(view); 
      loader.setController(this); 
      loader.load(); 
      System.out.println("\nKARDS!!\n"); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    @FXML 
    @Override 
    public void initialize() { 
    } 

    @Override 
    public Parent getView() { 
     return view ; 
    } 

    @FXML 
    @Override 
    public void eventOne() { 
     System.out.println("One"); 
    } 

    @FXML 
    @Override 
    public void eventTwo() { 
     loopToTen(); 
    } 

    @Override 
    public void eventThree() { 
     new Thread() { 
      @Override 
      public void run() { 
       loopToTen(); 
      } 
     }.start(); 
    } 

    private void loopToTen() { 
     for (int c = 0; c < 10; c++) { 
      try { 
       Thread.sleep(100); 
       System.out.println(c + 1); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 

    } 

} 

在這裏,而不是使MySceneImplBorderPane一個子類,它保持爲BorderPane實例的引用。該參考被傳遞給FXMLLoadersetRoot()方法,因此它將被填充FXML中定義的按鈕。最後,它從getView()方法返回。

現在只需更新Main類調​​用getView()

// primaryStage.setScene(new Scene((Parent) scene)); 
primaryStage.setScene(new Scene(scene.getView())); 
+0

謝謝,這解決了我遇到的異常。但是方法攔截器只有在手動調用MyScene接口的方法時纔會執行。當調用javafx階段的按鈕時,方法攔截器不會觸發。 –

+0

啊,是的。問題在於你正在調用'loader.setController(this)',顯式傳遞'this'引用。由於此處調用的構造函數是在目標對象上調用的(即不在代理上),所以'FXMLLoader'引用的控制器是目標,而不是代理。所以處理器方法沒有被修飾。你很可能不得不重構這一點。特別是'FXMLLoader'必須從應用程序上下文中生成控制器:您可以使用控制器工廠來實現,但該工廠需要對應用程序上下文的引用。 –

+0

謝謝,我會盡力的。如果我能夠工作,請回到你身邊,再次感謝! –