在使用Spring增强时,增强被织入目标类的所有方法中,假设希望有选择地织入目标类某些特定的方法中,就需要使用切点进行目标连接点的定位了。增强提供了连接点方位信息:如织入方法前面、后面等,而切点进一步描述织入哪些类的哪些方法上。Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息。
切面可以分为3类:一般切面、切点切面和引介切面
Advisor
代表一般切面,它仅包含一个Advice。Advice本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为横切面太宽泛,所以一般不会直接使用。
PointcutAdvisor
代表具有切点的切面,它包含Advice 和Pointcut 两个类,这样就能通过类、方法名以及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面。
IntroductionAdvisor
代表引介切面。引介切面是对应引介增强的特殊切面,它应用于类的层面上,所以引介切点使用ClassFilter进行定义。
静态普通方法名匹配切面 Cat类
1 2 3 4 5 6 7 8 public class Cat { public void eatIng (String name) { System.out.println(" I'm is Cat I'm eating" ); } public void eatEed (String name) { System.out.println(" I'm is Cat I'm finished" ); } }
Dog类
1 2 3 4 5 6 7 8 9 public class Dog { public void eatIng (String name) { System.out.println(" I'm is Dog I'm eating" ); } public void eatEed (String name) { System.out.println(" I'm is Dog I'm finished" ); } }
两个类都拥有两个方法名一样的方法,现在再Cat#eatEed方法调用后织入一个增强。用StaticMethodMatcherPointcutAdvisor 定义一个切面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import org.springframework.aop.ClassFilter;import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;import java.lang.reflect.Method;public class EatAdvisor extends StaticMethodMatcherPointcutAdvisor { public boolean matches (Method method, Class<?> aClass) { return "eatEed" .equals(method.getName()); } public ClassFilter getClassFilter () { return new ClassFilter() { public boolean matches (Class<?> aClass) { return Cat.class.isAssignableFrom(aClass); } }; } }
此抽象类唯一需要定义的是matches方法。在默认情况下,该切面匹配所有的类,通过覆盖getClassFilter()方法,让它仅匹配Cat以及其子类。
增强类
1 2 3 4 5 6 public class After implements AfterReturningAdvice { public void afterReturning (Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println(o1.getClass().getName()+"." +method.getName()); System.out.println("瞄!瞄!瞄! 谢谢老板的饭。" ); } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class AdvisorTest { static ProxyFactory pf=new ProxyFactory(); static ProxyFactory pf1=new ProxyFactory(); static Cat a=new Cat(); static Dog d=new Dog(); static EatAdvisor e=new EatAdvisor(); static AfterAdvice b=new After(); public static void main (String[] args) { String name="旺财" ; pf.setTarget(a); pf1.setTarget(d); e.setAdvice(b); pf.addAdvisor(e); pf1.addAdvisor(e); Cat c1=(Cat)pf.getProxy(); Dog d1=(Dog)pf1.getProxy(); c1.eatIng(name); System.out.println(); c1.eatEed(name); System.out.println(); d1.eatIng(name); System.out.println(); d1.eatEed(name); } }
结果:
如图可见,切面只织入Cat.eatEed()方法,并没有织入其他的方法。
自动创建代理 在前面都是通过ProxyFactoryBean创建织入切面的代理。Spring提供了自动代理机制。在内部,Spring使用BeanPostProcessor自动完成这项工作。
Spring提供了3个基于BeanPostProcessor的自动代理创建器实现类:
BeanNameAutoProxyCreator
基于Bean配置名规则的自动代理创建器。
DefaultAdvisorAutoProxyCreator
基于Advisor匹配机制的自动代理创建器。
AnnotationAwareAspectJAutoProxyCreator
基于Bean中AspectJ注解标签的自动代理创建器。
举一个DefaultAdvisorAutoProxyCreator的例子,给类方法名中带有”Ing”的方法织入一个前置增强。
XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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 ="c1" class ="org.zmd.model.Cat" /> <bean id ="d1" class ="org.zmd.model.Dog" /> <bean id ="eatAdvice" class ="org.zmd.Advice.Before" /> <bean id ="eatAdvisor" class ="org.springframework.aop.support.RegexpMethodPointcutAdvisor" p:advice-ref ="eatAdvice" p:patterns =".*Ing.*" /> <bean class ="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" /> </beans >
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 public class AdvisorTest1 { public static void main (String[] args) { String path="Advisor/EndAdvisor.xml" ; String name="旺财" ; ApplicationContext con=new ClassPathXmlApplicationContext(path); Cat c=con.getBean("c1" ,Cat.class); Dog d=con.getBean("d1" ,Dog.class); c.eatIng(name); c.eatEed(name); d.eatIng(name); d.eatEed(name); } }
结果:
可见只eatEed()方法并没有被增强,而eatIng()方法被增强了。
基于@AspectJ注解配置切面 @AspectJ使用JDK 5.0注解和正规的AspectJ的切点表达式语言描述切面。直接举个栗子:
配置切面类
@Aspect注解表示这是一个切面,**@Before、 @AfterReturning、分别表示前置增强、后置增强。此外还有 @Around**(环绕增强)、**@AfterThrowing**(抛出增强)、**@After**(表示Final增强,不管是抛出异常还是正常退出,该增强都会执行,相对于@AfterThrowing和@AfterReturning的混合物)、**@DeclareParents**(引介增强)。value 参数代表切点信息,argName 表示方法的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import org.aspectj.lang.annotation.*;@Aspect public class AspectTest { @Before(value = "execution(* eatIng(..)) && args(name)" ,argNames = "name") public void BeforeTest (String name) { System.out.print("I is BeforeVdvice use execution! I is " +name+"---" ); } @AfterReturning(value = "@annotation(org.zmd.Advisor.Aspect.NeedTest) && args(name)",argNames = "name") public void AfterTest (String name) { System.out.println("I is AfterVdvice use @annotation()! I is " +name+"---" ); } @Before(value = "within(org.zmd.model.*) && args(name)", argNames = "name") public void BeforeTest1 (String name) { System.out.print("I is BeforeVdvice use within()! I is " +name+"---" ); } @Before(value = "target(org.zmd.model.Cat) && args(name)", argNames = "name") public void AroundTest (String name) { System.out.print("I is BeforeVdvice use target()! I is " +name+"---" ); } @AfterReturning(value = "@within(org.zmd.Advisor.Aspect.NeedTest1) && args(name)", argNames = "name") public void AfterTest1 (String name) { System.out.println("I is AfterVdvice use @within()! I is " +name+"---" ); } }
execution(方法匹配模式串)
表示满足某一匹配模式的所有目标类方法连接点。如上面匹配目标类中的所有eatIng()方法。
@annotation(方法注解类名)
表示标注了特定注解的目标方法连接点。如上面匹配所有标注了@NeedTest的方法。
1 2 public @interface NeedTest {}
within(类名匹配串)
表示特定域下的所有连接点。如上面匹配org.zmd.model包里所有连接点。
target(类名)
匹配类中的所有连接点。如上面匹配Cat类中的所有连接点。
@within(类型注解类名)
匹配类标注特定注解的所有连接点。如上面匹配标了@NeedTest1的类的所有连接点。
还有很多切点函数这里就不一一详述了。
Model类
1 2 3 4 5 6 7 8 9 public class Cat { @NeedTest public void eatIng (String name) { System.out.println(" I'm is Cat I'm eating" ); } public void eatEed (String name) { System.out.println(" I'm is Cat I'm finished" ); } }
1 2 3 4 5 6 7 8 public class Dog { public void eatIng (String name) { System.out.println(" I'm is Dog I'm eating" ); } public void eatEed (String name) { System.out.println(" I'm is Dog I'm finished" ); } }
1 2 3 4 5 6 7 8 9 10 @NeedTest1 public class Pig { public void eatIng (String name) { System.out.println(" I'm is Pig I'm eating" ); } @NeedTest public void eatEed (String name) { System.out.println(" I'm is Pig I'm finished" ); } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class AspectTset { static Cat c,c1; static Dog d,d1; static Pig p,p1; static AspectJProxyFactory tf,tf1,tf2; public static void main (String[] args) { init(); c1.eatIng("小猫" ); c1.eatEed("小猫" ); d1.eatIng("小狗" ); d1.eatEed("小狗" ); p1.eatIng("小猪" ); p1.eatEed("小猪" ); } static public void init () { c=new Cat(); c1=new Cat(); d=new Dog(); d1=new Dog(); p=new Pig(); p1=new Pig(); tf=new AspectJProxyFactory(); tf1=new AspectJProxyFactory(); tf2=new AspectJProxyFactory(); tf.setTarget(c); tf1.setTarget(d); tf2.setTarget(p); tf.addAspect(AspectTest.class); tf1.addAspect(AspectTest.class); tf2.addAspect(AspectTest.class); c1=tf.getProxy(); d1=tf1.getProxy(); p1=tf2.getProxy(); } }
结果:
通过配置使用@AspectJ 通过编程的方式完成织入的工作较为麻烦,一般情况下,都是通过Spring的配置完成切面织入工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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:aop ="http://www.springframework.org/schema/aop" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > <aop:aspectj-autoproxy /> <bean id ="c" class ="org.zmd.model.Cat" /> <bean id ="d" class ="org.zmd.model.Dog" /> <bean id ="p" class ="org.zmd.model.Pig" /> <bean class ="org.zmd.Advisor.Aspect.AspectTest" /> </beans >
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class AspectTest1 { public static void main (String[] args) { String path="Advisor/AspectTest.xml" ; ApplicationContext con=new ClassPathXmlApplicationContext(path); Cat c=con.getBean("c" ,Cat.class); Dog d=con.getBean("d" ,Dog.class); Pig p=con.getBean("p" ,Pig.class); c.eatIng("小猫" ); c.eatEed("小猫" ); d.eatIng("小狗" ); d.eatEed("小狗" ); p.eatIng("小猪" ); p.eatEed("小猪" ); } }
效果和上面一样。可以看出通过配置完成切面织入工作会显得简便多了。
基于Schema配置切面 XML
配置里通过<aop:pointcut >定义了4个切点。通过<<aop:aspect >定义切面,切面里可以包含多个增强,<aop:config >里可以定义多个切面。proxy-target-class属性为true时,切面均使用CGLib动态代理技术。如果在<aop:config >下直接定义<aop:pointcut >,必须保证在<aop:aspect >之前定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?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:aop ="http://www.springframework.org/schema/aop" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > <aop:config proxy-target-class ="true" > <aop:pointcut id ="catEatIng" expression ="target(org.zmd.model.Cat) and execution(* eatIng(..)) and args(name)" /> <aop:pointcut id ="dogBad" expression ="target(org.zmd.model.Dog) and execution(* Bad(..)) and args(name)" /> <aop:pointcut id ="getName" expression ="target(org.zmd.model.Cat) and execution(* getName(..))" /> <aop:aspect ref ="adviceMothods" > <aop:before method ="eatIng" pointcut-ref ="catEatIng" /> <aop:after-returning method ="getName" pointcut-ref ="getName" returning ="name" /> <aop:before method ="beforeAdvice" pointcut-ref ="getName" /> <aop:after-throwing method ="Bad" pointcut-ref ="dogBad" /> </aop:aspect > </aop:config > <bean id ="adviceMothods" class ="org.zmd.Advisor.Schema.AdviceMethods" /> <bean class ="org.zmd.model.Cat" /> <bean class ="org.zmd.model.Dog" /> </beans >
增强方法类
里面包含了增强的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class AdviceMethods { public void eatIng (String name) { System.out.println("I is " +name+" from Schema!" ); } public void getName (String name) { System.out.println("I is " +name+" from after-returning!" ); } public void beforeAdvice () { System.out.println("I is beforeAdvice!" ); } public void Bad (String name) { System.out.println("I is " +name+" from after-throwing!" ); } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class SchemaTest { public static void main (String[] args) { String path="Advisor/SchemaTest.xml" ; ApplicationContext con=new ClassPathXmlApplicationContext(path); Cat c=con.getBean(Cat.class); Dog d=con.getBean(Dog.class); c.setName("小猫" ); c.getName(); System.out.println("----------------" ); c.eatIng("旺财" ); System.out.println("----------------" ); d.Bad("广生" ); } }
结果