Spring AOP:切面开发详解


  在 《Spring AOP增强》一文中,演示了各种增强的实现方式。增强提供了连接点的方位信息:如织入方法前面、后面等。但是增强会被织入目标类的所有方法,如何像手术刀一样找到目标方法,进行增强的织入,就要配合切点。

  切点进一步描述织入哪些类的哪些方法上。切点和增强组合起来我们称为切面。当然,增强本身就包含了一部分连接点信息,所以增强类本身就是一个普通的切面。但是这个普通切面还不够精确,如果需要更精确的增强织入,就需要配合切点,组成即另两种切面:切点切面 和引介切面。

在讨论切面之前,我们先看看切点。

切点的类型

  Spring通过org.springframework.aop.Pointcut接口描述切点。Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定的类上,通过MethodMatcher定位到某些特定的方法上,从而实现了精确的定位。

在这里插入图片描述

Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器,一般情况下,动态匹配不常使用。方法匹配器的类型由isRuntime()返回值决定默认是false表示静态方法匹配。

Spring中定义了6中类型的切点:

  • 1.静态方法切点:

    rg.springframework.aop.support.StaticMethodMatcherPointcut

    是静态方法切点的抽象基类,默认情况下它匹配所有的类。StaticMethodMatcherPointcut包括两个主要的子类,分别是NameMatchMethodPointcutAbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名,而后者使用正则表达式匹配方法签名。

  • 2.动态方法切点:

  • 3.注解切点:【下次讨论】

  • 4.表达式切点:【下次讨论】

  • 5.流程切点:

  • 6.复合切点:

切面的类型

  Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息。切面分为三类:一般切面、切点切面和引介切面。

  • Advisor:

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

  • PointcutAdvisor:

    代表具有切点的切面,它包含Advice和Pointcut两个类。这是最常用的一种切面,也是本文讨论的重点。

    它常用的实现类有:(后边也会围绕他们进行讨论)

在这里插入图片描述

  • IntroductionAdvisor:

    代表引介切面。

  这里我们主要讨论PointcutAdvisor,它有两个常用的实现类:

StaticMethodMatcherPointcutAdvisor:普通方法名匹配切面

regexpMethodPointcutAdvisor:正则表达式匹配切面

普通方法名匹配切面

StaticMethodMatcherPointcutAdvisor代表一个静态方法匹配切面,它通过StaticMethodMatcher Pointcut(即静态方法切点)定义切点,通过类过滤和方法名匹配定义切点。

使用步骤:

  • 1.定义目标对象和增强实现类:

    目标对象:

//目标对象Waiter
public class Waiter {

    public void greetTo(String name) {
        System.out.println("waiter greet to " + name + "...");
    }

    public void serveTo(String name) {
        System.out.println("waiter serving " + name + "...");
    }
}
//目标对象Seller
public class Seller {
    public void greetTo(String name) {
        System.out.println("seller greet to " + name + "...");
    }
}

  对象Waiter中有两个方法:greetTo和serveTo.对象Seller中有一个同名的方法greetTo

增强实现类:这里定义一个前置增强。织入增强后,会在方法执行前打印一句话。

public class GreetingBeforeAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object obj) throws Throwable {
        String clientName = (String) args[0];
        System.out.println("How are you!Mr." + clientName + ".");
    }
}
  • 2.定义切面对象,这里只需要继承StaticMethodMatcherPointcutAdvisor,实现的两个方法分别用于过滤类和方法名,这样就能保证定位到具体类的具体方法上,进行增强的织入。
/**
 * 定义一个切面
 */
public class StaticMethodMatcherAdvisor extends StaticMethodMatcherPointcutAdvisor {

    /**
     * 用于匹配方法
     * @param method
     * @param clazz
     * @return
     */
    public boolean matches(Method method, Class clazz) {
        return "greetTo".equals(method.getName());
    }

    /**
     * 用于匹配类
     * @return
     */
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            public boolean matches(Class clazz) {
                return Waiter.class.isAssignableFrom(clazz);
            }
        };
    }
}
  • 3.测试验证:首先我们通过编码的方式实现。随后再演示如何通过配置文件实现。
public class StaticMethodMatcherAdvisorTest {

    private Waiter waiter;
    private Seller seller;
    private ProxyFactory pfWaiter;
    private ProxyFactory pfSaller;
    private PointcutAdvisor advisor;//切面
    private Advice advice;//增强

    @BeforeClass
    public void init(){
        waiter = new Waiter();
        seller = new Seller();
        advisor = new StaticMethodMatcherAdvisor();
        advice = new GreetingBeforeAdvice();
        ((StaticMethodMatcherAdvisor) advisor).setAdvice(advice);

        //waiter的代理类
        pfWaiter = new ProxyFactory();
        pfWaiter.setTarget(waiter);
        pfWaiter.addAdvisor(advisor);
        //seller的代理类
        pfSaller = new ProxyFactory();
        pfSaller.setTarget(seller);
        pfSaller.addAdvisor(advisor);

    }

    @Test
	public  void staticMethod() {
        //获取代理进行方法调用
        Waiter proxy = (Waiter) pfWaiter.getProxy();
        proxy.greetTo("John");
        proxy.serveTo("John");

        //获取代理进行方法调用
        Seller proxy2 = (Seller) pfSaller.getProxy();
        proxy2.greetTo("John");
	}
}

  我们在测试类中首先定义了相关的目标对象,代理工厂和将要织入的增强。在初始化中,把增强和切面关联,同时构造了目标对象的代理工厂并将增强和期关联。最后,通过代理工厂获取代理类,进行调用。

最终打印结果如下:

How are you!Mr.John.  //增强中的输出
waiter greet to John...
waiter serving John...
seller greet to John...

可以看到,只有Waiter中的greetTo方法被织入了增强,说明切面的过滤已经生效。

如何通过配置文件实现?

  上面我们通过编码的方式实现目标对象、增强、切面还有代理类之间的相关装配。采用配置文件同样可以实现,本质上是一样,只不过把代码的逻辑采用了配置的方式实现。我们只需要将上面的第3步替换成以下两步:

1.编写配置文件:

<?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-3.1.xsd">

	<!-- 普通方法名匹配切面 -->
	<bean id="waiterTarget" class="com.smart.advisor.Waiter" />
	<bean id="sellerTarget" class="com.smart.advisor.Seller" />
	<bean id="greetingAdvice" class="com.smart.advisor.GreetingBeforeAdvice" />
	<bean id="greetingAdvisor" class="com.smart.advisor.StaticMethodMatcherAdvisor"
		p:advice-ref="greetingAdvice" />

	<bean id="parent" abstract="true"
		class="org.springframework.aop.framework.ProxyFactoryBean"
		p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true" />
	<bean id="waiter" parent="parent" p:target-ref="waiterTarget" />
	<bean id="seller" parent="parent" p:target-ref="sellerTarget" />

</beans>

  说明:我们首先配置了两个目标对象和增强类,紧接着我们配置了一个切面类,它指向:com.smart.advisor.StaticMethodMatcherAdvisor,并通过advice-ref属性关联了上面配置的增强。

因为两个目标类使用相同的切面,所以配置了一个parent节点,用以简化。其中p:interceptorNames属性指明了切面,p:proxyTargetClass="true"表明使用CGLib代理。最后配置两个代理类,通过target-ref属性和目标对象进行关联。

  1. 使用配置文件:
public class StaticMethodMatcherAdvisorXMLTest {

    private String configPath ="";
    private ApplicationContext ctx =null;

    @BeforeClass
    public void init(){
        configPath = "com/smart/advisor/beans.xml";
        ctx = new ClassPathXmlApplicationContext(configPath);
    }

    @Test
    public  void staticMethod() {
        Waiter waiter = (Waiter)ctx.getBean("waiter");
        Seller seller = (Seller)ctx.getBean("seller");
        waiter.greetTo("John");
        waiter.serveTo("John");
        seller.greetTo("John");
    }
}

  说明:通过ClassPathXmlApplicationContext加载配置文件。获取对应的代理对象,进行方法调用。结果和上面通过代码完全一样。这种方式,代码比较简洁,对象的依赖关系,配置在xml中统一管理。

小结:

  通过方法名匹配生成切面,可以对具体的方法进行增强织入,但是这种方式不够灵活,假设目标类中有多个方法,且命名有一定的规范,使用正则表达式匹配描述就灵活的多。

静态正则表达式方法匹配切面

下面直接使用RegexpMethodPointcutAdvisor,通过配置的方式为Waiter目标类定义一个切面。

1.在配置文件中增加正则表达式的切面配置和另一个代理对象:

<!-- 正则表达式方法名匹配切面 -->
	<bean id="regexpAdvisor"
		  class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
		  p:advice-ref="greetingAdvice">
		<property name="patterns">
			<list>
				<value>.*greet.*</value>
			</list>
		</property>
	</bean>
	<bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean"
		  p:interceptorNames="regexpAdvisor" p:target-ref="waiterTarget"
		  p:proxyTargetClass="true" />

2.代码测试:

public class RegexpAdvisorTest {
    private String configPath ="";
    private ApplicationContext ctx =null;

    @BeforeClass
    public void init(){
        configPath = "com/smart/advisor/beans.xml";
        ctx = new ClassPathXmlApplicationContext(configPath);
    }

    @Test
	public  void regexpAdvisor() {
		Waiter waiter = (Waiter)ctx.getBean("waiter1");
		waiter.greetTo("John");
		waiter.serveTo("John");
	}
}

输出结果: 匹配规则生效,只有greetTo方法被织入增强

How are you!Mr.John.
waiter greet to John...
waiter serving John...
总结:

  Spring中增强包含了要织入的代码和部分连接点信息,为了更精确的进行增强织入,需要配合切点精确定位。所以我们引出了切面的概念,切面就是增强和切点相结合。切点有6中类型,我们讨论最常用的静态方法切点,和与之对应的两个常用切面:StaticMethodMatcherAdvisor、RegexpMethodPointcutAdvisor。前者通过方法名和类过滤器,来确定对哪个类的哪个方法进行织入增强。后者定义了匹配的正则表达式规则,更加灵活。

  同时我们采用代码和配置文件两种不同的方式实现了相同的逻辑,可以看出,两者的本质和逻辑几乎一致,只是不同的表现形式。这也是Spring一贯的风格,包容开放。我们似乎感觉到通过配置文件能简化代码,但是配置xml文件的工作依然繁琐无趣,而这正是SpringBoot目前给我们解决的一个痛点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值