Spring-切面

Spring-切面

  在使用Spring增强时,增强被织入目标类的所有方法中,假设希望有选择地织入目标类某些特定的方法中,就需要使用切点进行目标连接点的定位了。增强提供了连接点方位信息:如织入方法前面、后面等,而切点进一步描述织入哪些类的哪些方法上。Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息。

切面可以分为3类:一般切面、切点切面和引介切面

  • Advisor

      代表一般切面,它仅包含一个Advice。Advice本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为横切面太宽泛,所以一般不会直接使用。

  • PointcutAdvisor

      代表具有切点的切面,它包含AdvicePointcut两个类,这样就能通过类、方法名以及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面。

  • 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) {// 切点方法匹配规则:方法名为eatEed
return "eatEed".equals(method.getName());
}

public ClassFilter getClassFilter(){//切点类匹配规则:为Cat的类或者子类
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.*"/> <!--向Advisor切面注入一个前置增强并匹配方法名带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">
<!--切点声明-->
<!--Cat类eatIng方法切点-->
<aop:pointcut id="catEatIng" expression="target(org.zmd.model.Cat) and execution(* eatIng(..)) and args(name)"/>
<!--Dog类Bad方法切点-->
<aop:pointcut id="dogBad" expression="target(org.zmd.model.Dog) and execution(* Bad(..)) and args(name)"/>
<!--Cat类getName方法切点-->
<aop:pointcut id="getName" expression="target(org.zmd.model.Cat) and execution(* getName(..))"/>

<!--切面声明-->
<aop:aspect ref="adviceMothods">
<!--使用增强类里的eatIng方法-->
<aop:before method="eatIng" pointcut-ref="catEatIng"/>
<!--使用增强类里的getName方法-->
<aop:after-returning method="getName" pointcut-ref="getName" returning="name"/>
<!--使用增强类里的beforeAdvice方法-->
<aop:before method="beforeAdvice" pointcut-ref="getName"/>
<!--使用增强类里的Bad方法-->
<aop:after-throwing method="Bad" pointcut-ref="dogBad"/>
</aop:aspect>
</aop:config>
<!--增强方法所在类的Bean-->
<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("广生");
}
}

结果


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×