[Spring] 面向切面
一、概述
按照軟件重構思想的理念,如果多個類中出現相同的代碼,應該考慮定義共同的抽象類。但並非所有情況下上述方法都是可行的,有時我們無法通過父類的方式消除重復性的橫切代碼,因為這些橫切邏輯依附在業務類方法的流程中,不能轉移到其他地方去。
面向切面編程(AOP)通過橫向抽取機制為這類無法通過縱向繼承進行抽象的重復性代碼提供了解決方案。此前需要了解一些AOP概念
- 連接點(Joinpoint):程序執行的某個特定位置,如類初始化前後、方法調用前後等,Spring僅支持方法的連接點。
- 切點(Pointcut):AOP通過切點定位特定連接點,一個切點可以匹配多個連接點。
- 增強(Advice):織入到目標類連接點上的一段代碼,同時包含了用於定位連接點的方位信息。
- 目標對象(Target):增強邏輯織入的目標類。
- 引介(Introduction):動態為類添加特定接口的實現邏輯,使該類成為接口的實現類。
- 織入(Weaving):將增強添加到目標對象的連接點上的過程,Spring采用的織入方式是動態代理織入。
- 代理(Proxy):當一個類被AOP織入增強後會產生一個結合了原類和增強邏輯的代理類。
- 切面(Aspect):由切點和增強組成,包含了橫切邏輯的定義和連接點的定義。
AOP的工作重心在於如何將增強應用於目標對象的連接點上,主要內容包括如何通過切點和增強定位連接點和如何在增強中編寫切面的代碼。
二、基礎知識
具有橫切邏輯的代碼實例。
//Recorder.javaView Codepackage aop; public class Recorder { private long begin; private String method; public Recorder(String method) { begin = System.currentTimeMillis(); this.method = method; } public void print() { long end = System.currentTimeMillis(); System.out.println(String.format("%s: %d ms", method, end - begin)); } } //Monitor.java package aop; public class Monitor { private static ThreadLocal<Recorder> threadLocal = new ThreadLocal<>(); public static void begin(String method) { System.out.println("begin monitor"); Recorder recorder = new Recorder(method); threadLocal.set(recorder); } public static void end() { System.out.println("end monitor"); Recorder recorder = threadLocal.get(); recorder.print(); } } //Service.java package aop; public interface Service { void testMethod1(); void testMethod2(); } package aop; public class Service implements Service { @Override public void testMethod1() { Monitor.begin("aop.Service.testMethod1"); System.out.println("Hello!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Monitor.end(); } @Override public void testMethod2() { Monitor.begin("aop.Service.testMethod2"); System.out.println("Bonjour!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Monitor.end(); } } //Solution.java import aop.Service; class Solution { public static void main(String[] args) { Service service = new Service(); service.testMethod1(); service.testMethod2(); } }
JDK提供了動態代理技術,允許開發者在運行期創建接口的代理實例,該技術是實現AOP的基礎。動態代理主要涉及到的類為Proxy和InvocationHandler,使用InvocationHandler接口定義橫切邏輯後通過反射條用目標類的代碼,動態將橫切邏輯和業務邏輯編織在一起;Proxy利用InvocationHandler創建特定接口的實例,生成目標類代理對象。優化後代碼如下:
//ServiceImpl.java package aop; public class ServiceImpl implements Service { @Override public void testMethod1() { System.out.println("Hello!"); } @Override public void testMethod2() { System.out.println("Bonjour!"); } } //Solution.java import aop.Service; import aop.ServiceHandler; import aop.ServiceImpl; import java.lang.reflect.Proxy; class Solution { public static void main(String[] args) { Service service = new ServiceImpl(); ServiceHandler handler = new ServiceHandler(service); Service proxy = (Service) Proxy.newProxyInstance( service.getClass().getClassLoader(), service.getClass().getInterfaces(), handler); proxy.testMethod1(); proxy.testMethod2(); } }View Code
使用JDK創建代理有只能為接口創建實例代理的限制。CGLib彌補了該缺陷,通過采用底層字節碼技術為特定類創建其子類,並在子類中采用方法攔截的技術攔截所有父類方法的調用,之後順勢織入橫切邏輯,從而實現動態創建類的代理實例。
//CGLibProxy.java package aop; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CGLibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class cls) { enhancer.setSuperclass(cls); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Monitor.begin(String.format("%s.%s", o.getClass().getName(), method.getName())); Object obj = methodProxy.invokeSuper(o, objects); Thread.sleep(1000); Monitor.end(); return obj; } } //Solution.java import aop.CGLibProxy; import aop.ServiceImpl; class Solution { public static void main(String[] args) { CGLibProxy proxy = new CGLibProxy(); ServiceImpl service = (ServiceImpl) proxy.getProxy(ServiceImpl.class); service.testMethod1(); service.testMethod2(); } }View Code
三、增強類
按照增強在目標類方法的連接點位置,Spring的增強可分為五種類型
- 前置增強:在方法執行前實施增強
- 後置增強:在方法執行後實施增強
- 環繞增強:在方法執行前後實施增強
- 異常拋出增強:在方法拋出異常後實施增強
- 引介增強:在目標類中添加新的方法和屬性
ProxyFactory是上述的JDK代理和CGLib代理的實現,負責將Advice應用到目標類中並獲取代理類。
//Person.java package aop; public interface Person { void read(String s) throws Throwable; } //PersonImpl.java package aop; public class PersonImpl implements Person { @Override public void read(String s) throws Throwable { if (!s.isEmpty()) { System.out.println("I‘m reading " + s); } else { throw new IllegalArgumentException("I found nothing to read"); } } } //Singer.java package aop; public interface Singer { void sing(String s) throws Throwable; } //PersonBeforeAdvice.java package aop; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class PersonBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) { System.out.println("person before advice called"); } } //PersonAfterAdvice.java package aop; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class PersonAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) { System.out.println("person after advice called"); } } //PersonAroundAdvice.java package aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class PersonAroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("person around advice before proceed"); Object obj = methodInvocation.proceed(); System.out.println("person around advice after proceed"); return obj; } } //PersonThrowsAdvice.java package aop; import org.springframework.aop.ThrowsAdvice; import java.lang.reflect.Method; public class PersonThrowsAdvice implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object obj, RuntimeException e) { System.out.println("person throws advice called"); } } //PersonIntroduction.java package aop; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.DelegatingIntroductionInterceptor; public class PersonIntroduction extends DelegatingIntroductionInterceptor implements Singer { @Override public void sing(String s) { if (!s.isEmpty()) { System.out.println("I‘m singing " + s); } else { throw new IllegalArgumentException("I found nothing to sing"); } } @Override public Object invoke(MethodInvocation mi) throws Throwable { return super.invoke(mi); } } //Solution.java import aop.*; import org.springframework.aop.framework.ProxyFactory; class Solution { public static void main(String[] args) { Person person = new PersonImpl(); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(person); proxyFactory.setInterfaces(Person.class, Singer.class); proxyFactory.setOptimize(true); proxyFactory.addAdvice(new PersonBeforeAdvice()); proxyFactory.addAdvice(new PersonAfterAdvice()); proxyFactory.addAdvice(new PersonAroundAdvice()); proxyFactory.addAdvice(new PersonThrowsAdvice()); proxyFactory.addAdvice(new PersonIntroduction()); Object proxy = proxyFactory.getProxy(); try { ((Person) proxy).read("Hello!"); ((Singer) proxy).sing(""); } catch (Throwable throwable) { System.out.println(throwable.getMessage()); } } }View Code
ProxyFactoryBean負責為其他Bean創建代理實例,內部使用ProxyFactory來實現。其可配置屬性如下
- target:代理的目標對象。
- interfaces:代理所實現的接口。
- interceptorNames:植入目標對象的Bean列表
- singleton:對立是否為單實例。
- optimize:是否強制使用CGLib。(CGLib創建代理時速度慢,但代理對象運行效率高。JDK代理相反)
- proxyTargetClass:是否對類進行代理。
通過ProxyFactoryBean可以實現XML配置代理。
//spring-config.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="beforeAdvice" class="aop.PersonBeforeAdvice"/> <bean id="afterAdvice" class="aop.PersonAfterAdvice"/> <bean id="aroundAdvice" class="aop.PersonAroundAdvice"/> <bean id="throwsAdvice" class="aop.PersonThrowsAdvice"/> <bean id="introduction" class="aop.PersonIntroduction"/> <bean id="person" class="aop.PersonImpl"/> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean" p:optimize="true" p:target-ref="person"> <property name="interfaces"> <list> <value>aop.Person</value> <value>aop.Singer</value> </list> </property> <property name="interceptorNames"> <list> <idref bean="beforeAdvice"/> <idref bean="afterAdvice"/> <idref bean="aroundAdvice"/> <idref bean="throwsAdvice"/> <idref bean="introduction"/> </list> </property> </bean> </beans> //Solution.java import aop.Person; import aop.Singer; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Object proxy = context.getBean("proxy"); try { ((Person) proxy).read("Hello!"); ((Singer) proxy).sing(""); } catch (Throwable throwable) { System.out.println(throwable.getMessage()); } } }View Code
四、切面
在上述代碼中,增強會被織入類的所有方法中。假如我們希望有選擇地將增強織入,就需要使用切點進行目標連接點地定位了。Spring通過Pointcut接口描述切點,Pointcut由ClassFilter和MethodMatcher組成,它通過ClassFilter定位至特定類,再通過MethodMatcher定位至特定方法。Spring提供了靜態方法切點、動態方法切點、註解切點、表達式切點、流程切點、復合切點六種類型的切點。
由於增強既包含橫切代碼,又包含了部分連接點信息,所以我們通過增強生成切面。Spring使用Advisor表示切面的概念,一個切面同時包含橫切代碼和連接點信息。切面分為Advisor、PointcutAdvisor和IntroductionAdvisor,其中PointcutAdvisor的具體實現類如下
- DefaultPointcutAdvisor:可通過任意Pointcut和Advice定義切面
- NameMatchMethodPointcutAdvisor:按方法名定義切點的切面
- RegexpMethodPointcutAdvisor:按正則表達式匹配方法名定義切面
- StaticMethodMatcherPointcutAdvisor:靜態方法匹配器切點定義的切面
- AspectJExpressionPointcutAdvisor:通過AspectJ切點表達式定義切點的切面
- AspectJPointcutAdvisor:通過AspectJ語法定義切點的切面
靜態方法匹配器僅對方法的簽名進行一次匹配,靜態普通方法及靜態正則表達式方法匹配切面代碼示例如下
//Person.java package aop; public class Person { public void read(String s) { System.out.println("I‘m reading " + s); } public void sing(String s) { System.out.println("I‘m singing " + s); } } //PersonReadAdvisor.java package aop; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; import java.lang.reflect.Method; public class PersonReadAdvisor extends StaticMethodMatcherPointcutAdvisor { @Override public boolean matches(Method method, Class<?> aClass) { return method.getName().endsWith("read"); } @Override public ClassFilter getClassFilter() { return Person.class::isAssignableFrom; } } //spring-config.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="aop.PersonAroundAdvice" id="aroundAdvice"/> <bean class="aop.PersonBeforeAdvice" id="beforeAdvice"/> <bean class="aop.PersonAfterAdvice" id="afterAdvice"/> <bean class="aop.PersonReadAdvisor" id="aroundReadAdvisor" p:advice-ref="aroundAdvice"/> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="beforeSingAdvisor" p:advice-ref="beforeAdvice" p:pattern=".*sing"/> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="afterSingAdvisor" p:advice-ref="afterAdvice" p:pattern=".*sing"/> <bean class="aop.Person" id="person"/> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:target-ref="person" p:proxyTargetClass="true"> <property name="interceptorNames"> <list> <idref bean="aroundReadAdvisor"/> <idref bean="beforeSingAdvisor"/> <idref bean="afterSingAdvisor"/> </list> </property> </bean> </beans> //Solution.java import aop.Person; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Person person = context.getBean("proxy", Person.class); person.read("Hello!"); person.sing("Goodbye!"); } }View Code
動態方法匹配器由於調用方法的入參不同每次調用都會進行依次匹配,會對性能造成影響。動態切面代碼示例如下
//ReadDynamicPointcut.java package aop; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.DynamicMethodMatcherPointcut; import java.lang.reflect.Method; public class ReadDynamicPointcut extends DynamicMethodMatcherPointcut { @Override public boolean matches(Method method, Class<?> targetClass) { System.out.println("static method matcher called"); return method.getName().endsWith("read"); } @Override public boolean matches(Method method, Class<?> aClass, Object... objects) { System.out.println("dynamic method matcher called"); return method.getName().endsWith("read"); } @Override public ClassFilter getClassFilter() { return Person.class::isAssignableFrom; } } //spring-config.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dynamicAdvisor"> <property name="advice"> <bean class="aop.PersonAroundAdvice"/> </property> <property name="pointcut"> <bean class="aop.ReadDynamicPointcut"/> </property> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:interceptorNames="dynamicAdvisor" p:proxyTargetClass="true"> <property name="target"> <bean class="aop.Person"/> </property> </bean> </beans>View Code
流程切點代表由某個方法直接或間接發起調用的其他方法。將特定類的所有方法交由代理類的一個方法,之後在運行期判斷方法調用棧堆中的方法是否滿足流程切點的要求。流程切面和動態切面一樣會對性能造成影響。流程切面代碼示例如下
//PersonDelegate.java package aop; public class PersonDelegate { private Person person; public void doAll(String s) { person.read(s); person.sing(s); } public void setPerson(Person person) { this.person = person; } } //spring-config.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="controlFlowAdvisor"> <property name="pointcut"> <bean class="org.springframework.aop.support.ControlFlowPointcut"> <constructor-arg index="0" value="aop.PersonDelegate"/> <constructor-arg index="1" value="doAll"/> </bean> </property> <property name="advice"> <bean class="aop.PersonAroundAdvice"/> </property> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:interceptorNames="controlFlowAdvisor" p:proxyTargetClass="true"> <property name="target"> <bean class="aop.Person"/> </property> </bean> </beans> //Solution.java import aop.Person; import aop.PersonDelegate; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Person person = context.getBean("proxy", Person.class); PersonDelegate delegate = new PersonDelegate(); delegate.setPerson(person); delegate.doAll("Hello!"); person.read("Goodbye!"); person.sing("Goodbye!"); } }View Code
切面的定義中只有一個切點,有時會不足以描述目標連接點的信息。假如我們希望在代理類中被調用的特定方法才織入增強,這樣由兩個或以上單獨切點確定的為復合切點。復合切面代碼示例如下
//SingComposablePointcut.java package aop; import org.springframework.aop.Pointcut; import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.ControlFlowPointcut; import org.springframework.aop.support.NameMatchMethodPointcut; public class SingComposablePointcut extends ComposablePointcut { public Pointcut getIntersectionPointcut() { ComposablePointcut composablePointcut = new ComposablePointcut(); ControlFlowPointcut controlFlowPointcut = new ControlFlowPointcut(PersonDelegate.class, "doAll"); NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut(); nameMatchMethodPointcut.addMethodName("sing"); return composablePointcut.intersection((Pointcut) controlFlowPointcut).intersection((Pointcut) nameMatchMethodPointcut); } } //spring-config.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="aop.SingComposablePointcut" id="composablePointcut"/> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="composableAdvisor" p:pointcut="#{composablePointcut.intersectionPointcut}"> <property name="advice"> <bean class="aop.PersonAroundAdvice"/> </property> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:interceptorNames="composableAdvisor" p:proxyTargetClass="true"> <property name="target"> <bean class="aop.Person"/> </property> </bean> </beans>View Code
引介切面是引介增強的封裝器,引介切面示例如下
//Poet.java package aop; public interface Poet { void write(String s); } //PersonIntroduction.java package aop; import org.springframework.aop.support.DelegatingIntroductionInterceptor; public class PersonIntroduction extends DelegatingIntroductionInterceptor implements Poet { @Override public void write(String s) { System.out.println("I‘m writing " + s); } } //spring-config.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.aop.support.DefaultIntroductionAdvisor" id="introductionAdvisor"> <constructor-arg> <bean class="aop.PersonIntroduction"/> </constructor-arg> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy" p:interceptorNames="introductionAdvisor" p:proxyTargetClass="true"> <property name="target"> <bean class="aop.Person"/> </property> </bean> </beans> //Solution.java import aop.Person; import aop.Poet; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Object obj = context.getBean("proxy"); ((Poet) obj).write("Hello!"); ((Person) obj).read("Goodbye!"); ((Person) obj).sing("Goodbye!"); } }View Code
五、自動代理
通過ProxyFactoryBean創建織入切面的代理時,每個需要被代理的Bean都使用單獨的ProxyFactoryBean進行配置。雖然可以使用父子<bean>來進行簡化,但在Bean的數量較多時配置依然繁瑣。為此Spring通過BeanPostProcessor接口提供了自動代理機制,讓容器自動生成代理而無需手動配置。其實現類如下
- BeanNameAutoProxyCreator:基於Bean配置名規則的自動代理創建器,允許為一組特定配置名的Bean自動創建代理示例。
- DefaultAdvisorAutoProxyCreator:基於Advisor配置機制的自動代理創建器,對容器中所有Advisor進行掃描並自動應用到匹配的Bean中。
- AnnotationAwareAspectJAutoProxyCreator:基於Bean中的@AspectJ註解標簽的自動代理創建器,為包含該註解的Bean自動創建代理實例。
通過Bean配置名進行自動代理代碼示例如下
//spring-config.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="aop.Person" id="person1"/> <bean class="aop.Person" id="person2"/> <bean class="aop.PersonAroundAdvice" id="aroundAdvice"/> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" p:beanNames="person1,person2" p:interceptorNames="aroundAdvice" p:optimize="true"/> <!-- Or beanNames="person*" --> </beans> //Solution.java import aop.Person; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Solution { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); Person person1 = context.getBean("person1", Person.class); Person person2 = context.getBean("person2", Person.class); person1.read("Hello!"); person2.sing("Goodbye!"); } }View Code
通過Advisor進行自動代理代碼示例如下
//spring-config.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="aop.Person" id="person1"/> <bean class="aop.Person" id="person2"/> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <bean class="aop.PersonReadPointcut"/> </property> <property name="advice"> <bean class="aop.PersonAroundAdvice"/> </property> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> </beans>View Code
[Spring] 面向切面