780 | | |
| 780 | = spring aop = |
| 781 | |
| 782 | 在一個服務的流程中插入與服務無關的邏輯(例如Logging、Security),這樣的邏輯稱為 Cross-cutting concerns,將 Crossing-cutting concerns 獨立出來為一個物件,這樣的特殊物件稱之為 Aspect,Aspect-oriented programming 著重在 Aspect 的設計及與應用程式的縫合(Weave)。 |
| 783 | |
| 784 | |
| 785 | 可以使用代理(Proxy)機制來解決這個問題,在這邊討論兩種代理方式:靜態代理(Static proxy)與動態代理(Dynamic proxy)。 |
| 786 | |
| 787 | 以下範例中,HelloProxy或是LogHandler,這樣的物件稱之為切面(Aspect) |
| 788 | |
| 789 | AOP中的Aspect所指的可以是像日誌等這類的動作或服務, |
| 790 | 您將這些動作(Cross-cutting concerns)設計為通用、 |
| 791 | 不介入特定業務物件的一個職責清楚的Aspect物件, |
| 792 | 這就是所謂的Aspect-oriented programming,縮寫名詞即為AOP。 |
| 793 | |
| 794 | Aspect可以獨立於應用程式之外,在必要的時候,可以介入應用程式之中提供服務; |
| 795 | 而不需要相關服務的時候,又可以將這些Aspect直接從應用程式中脫離, |
| 796 | 而您的應用程式本身不需修改任何一行程式碼。 |
| 797 | |
| 798 | |
| 799 | == 無 aop == |
| 800 | |
| 801 | * HelloSpeaker.java |
| 802 | |
| 803 | {{{ |
| 804 | #!java |
| 805 | package onlyfun.caterpillar; |
| 806 | |
| 807 | import java.util.logging.*; |
| 808 | |
| 809 | public class HelloSpeaker { |
| 810 | private Logger logger = |
| 811 | Logger.getLogger(this.getClass().getName()); |
| 812 | |
| 813 | public void hello(String name) { |
| 814 | // 方法執行開始時留下日誌 |
| 815 | logger.log(Level.INFO, "hello method starts...."); |
| 816 | // 程式主要功能 |
| 817 | System.out.println("Hello, " + name); |
| 818 | // 方法執行完畢前留下日誌 |
| 819 | logger.log(Level.INFO, "hello method ends...."); |
| 820 | } |
| 821 | } |
| 822 | }}} |
| 823 | 在HelloSpeaker類別中,當執行hello()方法時,您希望該方法執行開始與執行完畢時都能留下日誌,最簡單的作法就是如以上的程式設計,在方法執行的前後加上日誌動作,然而記錄的這幾行程式碼橫切入(Cross-cutting)HelloSpeaker類別中,對於 HelloSpeaker來說,日誌的這幾個動作並不屬於HelloSpeaker商務邏輯(顯示"Hello"等文字),這使得 HelloSpeaker增加了額外的職責 |
| 824 | |
| 825 | == 靜態代理 == |
| 826 | |
| 827 | |
| 828 | * IHello.java |
| 829 | |
| 830 | {{{ |
| 831 | #!java |
| 832 | package onlyfun.caterpillar; |
| 833 | |
| 834 | public interface IHello { |
| 835 | public void hello(String name); |
| 836 | } |
| 837 | }}} |
| 838 | |
| 839 | * HelloSpeaker.java |
| 840 | |
| 841 | {{{ |
| 842 | #!java |
| 843 | package onlyfun.caterpillar; |
| 844 | |
| 845 | public class HelloSpeaker implements IHello { |
| 846 | public void hello(String name) { |
| 847 | System.out.println("Hello, " + name); |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | * HelloProxy.java |
| 852 | |
| 853 | {{{ |
| 854 | #!java |
| 855 | |
| 856 | package onlyfun.caterpillar; |
| 857 | |
| 858 | import java.util.logging.*; |
| 859 | |
| 860 | public class HelloProxy implements IHello { |
| 861 | private Logger logger = |
| 862 | Logger.getLogger(this.getClass().getName()); |
| 863 | |
| 864 | private IHello helloObject; |
| 865 | |
| 866 | public HelloProxy(IHello helloObject) { |
| 867 | this.helloObject = helloObject; |
| 868 | } |
| 869 | |
| 870 | public void hello(String name) { |
| 871 | // 日誌服務 |
| 872 | log("hello method starts...."); |
| 873 | |
| 874 | // 執行商務邏輯 |
| 875 | helloObject.hello(name); |
| 876 | |
| 877 | // 日誌服務 |
| 878 | log("hello method ends...."); |
| 879 | } |
| 880 | |
| 881 | private void log(String msg) { |
| 882 | logger.log(Level.INFO, msg); |
| 883 | } |
| 884 | } |
| 885 | }}} |
| 886 | |
| 887 | * ProxyDemo.java |
| 888 | |
| 889 | {{{ |
| 890 | #!java |
| 891 | package onlyfun.caterpillar; |
| 892 | |
| 893 | public class ProxyDemo { |
| 894 | public static void main(String[] args) { |
| 895 | IHello proxy = |
| 896 | new HelloProxy(new HelloSpeaker()); |
| 897 | proxy.hello("Justin"); |
| 898 | } |
| 899 | } |
| 900 | |
| 901 | |
| 902 | == 動態 aop == |
| 903 | |
| 904 | jdk本身即有可協助開發動態代理功能的API等相關類別,您不必為特定物件與方法撰寫特定的代理物件,使用動態代理,可以使得一個處理者(Handler)服務於各個物件 |
| 905 | |
| 906 | 主要的概念是使用Proxy.newProxyInstance()靜態方法建立一個代理物件,建立代理物件時必須告知所要代理的介面, |
| 907 | |
| 908 | 操作所建立的代理物件,在每次操作時會呼叫InvocationHandler的invoke()方法,invoke()方法會傳入被代理物件的方法名稱與執行參數, |
| 909 | |
| 910 | 實際上要執行的方法交由method.invoke(),您在method.invoke()前後加上記錄動作,method.invoke()傳回的物件是實際方法執行過後的回傳結果。 |
| 911 | |
| 912 | 使用LogHandler的bind()方法來綁定被代理物件 |
| 913 | |
| 914 | |
| 915 | * LogHandler.java |
| 916 | |
| 917 | {{{ |
| 918 | #!java |
| 919 | package onlyfun.caterpillar; |
| 920 | |
| 921 | import java.util.logging.*; |
| 922 | import java.lang.reflect.*; |
| 923 | |
| 924 | public class LogHandler implements InvocationHandler { |
| 925 | private Logger logger = |
| 926 | Logger.getLogger(this.getClass().getName()); |
| 927 | |
| 928 | private Object delegate; |
| 929 | |
| 930 | public Object bind(Object delegate) { |
| 931 | this.delegate = delegate; |
| 932 | return Proxy.newProxyInstance( |
| 933 | delegate.getClass().getClassLoader(), |
| 934 | delegate.getClass().getInterfaces(), |
| 935 | this); |
| 936 | } |
| 937 | |
| 938 | public Object invoke(Object proxy, Method method, |
| 939 | Object[] args) throws Throwable { |
| 940 | Object result = null; |
| 941 | |
| 942 | try { |
| 943 | log("method starts..." + method); |
| 944 | |
| 945 | result = method.invoke(delegate, args); |
| 946 | |
| 947 | logger.log(Level.INFO, "method ends..." + method); |
| 948 | } catch (Exception e){ |
| 949 | log(e.toString()); |
| 950 | } |
| 951 | |
| 952 | return result; |
| 953 | } |
| 954 | |
| 955 | private void log(String message) { |
| 956 | logger.log(Level.INFO, message); |
| 957 | } |
| 958 | } |
| 959 | |
| 960 | }}} |
| 961 | |
| 962 | * (同前) IHello.java |
| 963 | |
| 964 | {{{ |
| 965 | #!java |
| 966 | package onlyfun.caterpillar; |
| 967 | |
| 968 | public interface IHello { |
| 969 | public void hello(String name); |
| 970 | } |
| 971 | }}} |
| 972 | |
| 973 | * (同前) HelloSpeaker.java |
| 974 | |
| 975 | {{{ |
| 976 | #!java |
| 977 | package onlyfun.caterpillar; |
| 978 | |
| 979 | public class HelloSpeaker implements IHello { |
| 980 | public void hello(String name) { |
| 981 | System.out.println("Hello, " + name); |
| 982 | } |
| 983 | } |
| 984 | }}} |
| 985 | |
| 986 | * ProxyDemo.java |
| 987 | |
| 988 | {{{ |
| 989 | #!java |
| 990 | package onlyfun.caterpillar; |
| 991 | |
| 992 | public class ProxyDemo { |
| 993 | public static void main(String[] args) { |
| 994 | LogHandler logHandler = new LogHandler(); |
| 995 | |
| 996 | IHello helloProxy = |
| 997 | (IHello) logHandler.bind(new HelloSpeaker()); |
| 998 | helloProxy.hello("Justin"); |
| 999 | } |
| 1000 | } |
| 1001 | }}} |
| 1002 | |
| 1003 | == 靜態與動態 == |
| 1004 | |
| 1005 | 動態aop 與 靜態 aop 中,(主要介面) I_Hello.java 與 (主要功能)HelloSpeaker.java 內容相同 |
| 1006 | |
| 1007 | B. 靜態aop |
| 1008 | |
| 1009 | 主要不同為: HelloProxy.java |
| 1010 | |
| 1011 | main 呼叫: IHello proxy = new HelloProxy(new HelloSpeaker()); |
| 1012 | |
| 1013 | |
| 1014 | |
| 1015 | C. 動態aop |
| 1016 | |
| 1017 | 主要不同為: LogHandler.java |
| 1018 | |
| 1019 | main 呼叫:IHello helloProxy = (IHello) logHandler.bind(new HelloSpeaker()); |
| 1020 | |
| 1021 | 其中 I_Hello 與 HelloSpeaker 相同於 動態aop 方法 |