菜鸟的成长之路——切面篇

  我们再来回忆一下切面是什么,切面就是将增强织入到某个切点。上节课我们知道,增强一方面包含了横切逻辑,另一方面包含了连接点(也就是方法的前面、后面、环绕等等),但是增强不能决定给哪个类的哪个方法横切(也就是不能决定切点),这节课我们就能找到这个切点,然后增强和切点共同组成切面。

1、如何研究切面比较简单?

  笼统来说,切面可以分成增强和切点,上节课我们讲过了很多增强,而切面也会有很多种类,可以说增强和切面两两组合可以组成切面,为了方便研究,本节课如果不特殊说明,都默认使用前置增强。
  切面分为以下三类:

  • Advisor:代表一般切面,他仅仅包含一个advice。简单来说就是一个增强给目标类的所有方法进行织入,我们上节课研究的就是一个一般切面。
  • PointcutAdvisor:代表切点切面,包含Advice和Pointcut两个类,他比一般切面多了切点,因此切面更加完整,所以很少用到上面的一般切面。
  • IntroductionAdvisor:代表引介切面,他是对应引介增强的切面,因此研究这个切面不能使用前置增强,必须使用引介增强。

  对于一般切面,我们上节课一直在说,这节课不再赘述。切点切面分很多种类,我们先对引介切面进行研究,然后再仔细研究切点切面。

2、引介切面如何使用?

  前面我们讲过了引介增强,其实引介切面就是对引介增强的一个包装,使用引介切面我们可以更简单灵活的为目标类添加接口。仍然使用上节课的案例,看一下引介切面的代码:
Lecturer.java(目标类)

package com.spring.test;

/**
 * 讲师类,具有上课方法
 * @author beijingduanluo
 *
 */
public class Lecturer {
    public void classBegin() {
        System.out.println("讲师传授新技术,同学们讨论很激烈!");
    }
}

School.java(目标接口)

package com.spring.test;
/**
 * 学校的接口,具有举办活动的方法
 * @author beijingduanluo
 *
 */
public interface School {
    public void setActive(boolean active);
}

LecturerAdvice.java(引介增强类)

package com.spring.test;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
/**
 * 本类继承IntroductionInterceptor接口的支持类DelegatingIntroductionInterceptor,成为引介增强
 * 本增强类实现需要添加的目标接口
 * @author beijingduanluo
 *
 */
public class LecturerAdvice extends DelegatingIntroductionInterceptor implements School{

    private boolean active;
    /**
     * 代理目标类实现接口的方法
     */
    public void setActive(boolean active) {
        // TODO Auto-generated method stub
        this.active = active;
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        // TODO Auto-generated method stub
//      如果目标类调用的不是classBegin方法,不进行横切逻辑
        if(!mi.getMethod().getName().equals("classBegin")) {
            super.invoke(mi);
            return null;
        }
//      判断学校是否有活动
        if(active) {
//          如果有活动,则停止讲师上课
            System.out.println("学校有活动,讲师暂停上课。");
        }else {
//          学校没有活动,讲师点名
            System.out.println("讲师开始点名。");
//          讲师正常上课
            super.invoke(mi);
        }
        return null;
    }

}

  前面的代码和引介增强没有什么区别,下面的开始和引介增强有了区别。
ApplicationContext.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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

    <!-- 配置目标类 -->
    <bean id="lecturer" class="com.spring.test.Lecturer"></bean>
    <!-- 配置引介切面,将引介增强包装进去 -->
    <bean id="advisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
        <constructor-arg>           
            <bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
        </constructor-arg>
    </bean>
    <!-- 配置代理工厂 -->
    <bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactory">
        <!-- 注入目标类 -->
        <property name="target" ref="lecturer"></property>
        <!-- 因为引介增强是继承关系,所以使用CGLib,进行下面修改 -->
        <property name="proxyTargetClass" value="true"></property>
        <!-- 没有去注入interfaces属性值,会默认将引介增强中所有接口给到目标类 -->
    </bean>
</beans>

SpringTest.java(测试类)

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvice;
import com.spring.test.School;

public class SpringTest{

    public static void main(String[] args) {
        ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
        DefaultIntroductionAdvisor advisor = (DefaultIntroductionAdvisor) content.getBean("advisor");
        ProxyFactory pf = (ProxyFactory) content.getBean("proxyFactory");
//      没有配置引介增强,而是配置引介增强的包装,引介切面
        pf.addAdvisor(advisor);
//      获取讲师的代理对象
        Lecturer lecturer = (Lecturer) pf.getProxy();
//      没有举办活动时候讲师上课
        lecturer.classBegin();
        System.out.println("-------------学校举办活动-------------");
//      此时已经将接口加到目标类Lecturer中,如果调用接口方法,需要将目标对象强转
        School school = (School) lecturer;
//      学校举办活动
        school.setActive(true);
//      举办活动的时候讲师上课
        lecturer.classBegin();
    }
}

3、切点切面的体系结构是什么样的?

  首先看一下切点切面PointcutAdvisor类的结构图:
这里写图片描述
  PointcutAdvisor类有六个具体的实现类,这六个实现类分别是上图中蓝色的部分。这些类没必要深入研究,我们知道有这么几个类,而且他们都实现PointcutAdvisor接口就可以了。
  那这些蓝色标准的类怎么使用呢?我们会在下面一一讨论。切点切面的匹配有这么几种方法:静态普通方法名匹配切面、静态正则表达式方法匹配切面、动态切面、流程切面、符合切点切面。我们只对两种静态切面进行研究。

4、静态普通方法名匹配切面是如何实现的?

  静态普通方法名匹配切面,通过扩展StaticMethodMatcherPointcutAdvisor类实现,是程序运行前就匹配好,而不是匹配静态方法。
  现有如下需求:

学校要求讲师在上课前进行点名。

  上代码:
Lecturer.java(目标类)

package com.spring.test;

/**
 * 讲师类,具有上课方法
 * @author beijingduanluo
 *
 */
public class Lecturer {
    public void classBegin() {
        System.out.println("讲师传授新技术,同学们讨论很激烈!");
    }
}

Others.java(目标类干扰类)

package com.spring.test;

/**
 * 讲师类的干扰类,判断是否定位到Lecturer类中。
 * @author beijingduanluo
 *
 */
public class Others {
    public void classBegin() {
        System.out.println("其他人上课不需要点名");
    }
}

LecturerAdvice.java(增强类)

package com.spring.test;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**
 * 使用前置增强
 * @author beijingduanluo
 *
 */
public class LecturerAdvice implements MethodBeforeAdvice{

    public void before(Method method, Object[] args, Object target) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("讲师开始点名");
    }


}

LecturerAdvisor.java(切面类)

package com.spring.test;

import java.lang.reflect.Method;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

public class LecturerAdvisor extends StaticMethodMatcherPointcutAdvisor{

    /**
     * 重写matches方法,匹配方法名,但是所有的类都会符合
     */
    public boolean matches(Method method, Class<?> targetClass) {
        // TODO Auto-generated method stub
//      匹配成功classBegin方法返回true
        return method.getName().equals("classBegin");
    }

    /**
     * 手动重写getClassFilter方法,匹配响应的类
     */
    @Override
    public ClassFilter getClassFilter() {
        // TODO Auto-generated method stub
//      返回带有Lecturer或者其子类的类过滤器
        return new ClassFilter() {
            public boolean matches(Class<?> clazz) {
                // TODO Auto-generated method stub
//              匹配Lecturer类或者其子类返回true
                return Lecturer.class.isAssignableFrom(clazz);
            }
        };
    }

}

applicationContext.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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

    <!-- 配置增强的bean -->
    <bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
    <!-- 配置切面的bean -->
    <bean id="advisor" class="com.spring.test.LecturerAdvisor">
        <!-- 将增强的bean注入到切面中 -->
        <property name="advice" ref="advice"></property>
    </bean>
    <!-- 分别创建两个目标类的bean -->
    <bean id="lecturer" class="com.spring.test.Lecturer"></bean>
    <bean id="others" class="com.spring.test.Others"></bean>

    <!-- 配置代理工厂 -->
    <bean id="proxyFactory" abstract="true" class="org.springframework.aop.framework.ProxyFactory"></bean>
    <!-- 通过继承上面的抽象bean创建Lecturer的代理工厂 -->
    <bean id="lecturerpf" parent="proxyFactory">
        <property name="target" ref="lecturer"></property>
    </bean>
    <!-- 通过继承上面的抽象bean创建Others的代理工厂 -->
    <bean id="otherspf" parent="proxyFactory">
        <property name="target" ref="others"></property>
    </bean>
</beans>

SpringTest.java(测试类)

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvisor;
import com.spring.test.Others;

public class SpringTest{

    public static void main(String[] args) {
        ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
        LecturerAdvisor advisor = (LecturerAdvisor) content.getBean("advisor");
        System.out.println("======lecturer的代理工厂,并调用Lecturer的方法");
        ProxyFactory lpf = (ProxyFactory) content.getBean("lecturerpf");
        lpf.addAdvisor(advisor);
        Lecturer lecturer = (Lecturer) lpf.getProxy();
        lecturer.classBegin();
        System.out.println("======Others的代理工厂,并调用Others的方法");
        ProxyFactory opf = (ProxyFactory) content.getBean("otherspf");
        opf.addAdvisor(advisor);
        Others others = (Others) opf.getProxy();
        others.classBegin();
    }
}

控制台打印

======lecturer的代理工厂,并调用Lecturer的方法
讲师开始点名
讲师传授新技术,同学们讨论很激烈!
======Others的代理工厂,并调用Others的方法
其他人上课不需要点名

  我们发现,对于Lecturer的classBegin方法,织入了增强,而对于Others的classBegin没有织入增强,这就是Advisor进行了类和方法的匹配的结果。

5、静态正则表达式方法匹配切面如何实现?

  静态正则表达式方法匹配切面扩展的是RegexpMethodPointcutAdvisor实现类,这个实现类已经有了完善的功能,我们只需要在配置文件中配置这个实现类,不需要重写去创建advisor了。代码如下:
Lecturer.java(目标类)

package com.spring.test;

/**
 * 讲师类,具有上课方法
 * @author beijingduanluo
 *
 */
public class Lecturer {
    public void classBegin() {
        System.out.println("讲师传授新技术,同学们讨论很激烈!");
    }
}

Others.java(干扰目标类)

package com.spring.test;

/**
 * 讲师类的干扰类,判断是否定位到Lecturer类中。
 * @author beijingduanluo
 *
 */
public class Others {
    public void classBegin() {
        System.out.println("其他人上课不需要点名");
    }
}

LecturerAdvice.java(增强类)

package com.spring.test;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**
 * 使用前置增强
 * @author beijingduanluo
 *
 */
public class LecturerAdvice implements MethodBeforeAdvice{

    public void before(Method method, Object[] args, Object target) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("讲师开始点名");
    }


}

ApplicationContext.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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

    <!-- 配置增强的bean -->
    <bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
    <!-- 配置切面的bean -->
    <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!-- 将增强的bean注入到切面中 -->
        <property name="advice" ref="advice"></property>
        <!-- 使用正则表达式匹配方法的全限定名 -->
        <property name="patterns">
            <list>
                <value>com.spring.test.Lecturer.classBegin</value>
            </list>
        </property> 
    </bean>
    <!-- 分别创建两个目标类的bean -->
    <bean id="lecturer" class="com.spring.test.Lecturer"></bean>
    <bean id="others" class="com.spring.test.Others"></bean>

    <!-- 配置代理工厂 -->
    <bean id="proxyFactory" abstract="true" class="org.springframework.aop.framework.ProxyFactory"></bean>
    <!-- 通过继承上面的抽象bean创建Lecturer的代理工厂 -->
    <bean id="lecturerpf" parent="proxyFactory">
        <property name="target" ref="lecturer"></property>
    </bean>
    <!-- 通过继承上面的抽象bean创建Others的代理工厂 -->
    <bean id="otherspf" parent="proxyFactory">
        <property name="target" ref="others"></property>
    </bean>
</beans>

SpringTest.java(测试类)

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.aop.support.RegexpMethodPointcutAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.test.Lecturer;
import com.spring.test.Others;

public class SpringTest{

    public static void main(String[] args) {
        ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
        RegexpMethodPointcutAdvisor advisor = (RegexpMethodPointcutAdvisor) content.getBean("advisor");
        System.out.println("======lecturer的代理工厂,并调用Lecturer的方法");
        ProxyFactory lpf = (ProxyFactory) content.getBean("lecturerpf");
        lpf.addAdvisor(advisor);
        Lecturer lecturer = (Lecturer) lpf.getProxy();
        lecturer.classBegin();
        System.out.println("======Others的代理工厂,并调用Others的方法");
        ProxyFactory opf = (ProxyFactory) content.getBean("otherspf");
        opf.addAdvisor(advisor);
        Others others = (Others) opf.getProxy();
        others.classBegin();
    }
}

控制台打印

======lecturer的代理工厂,并调用Lecturer的方法
讲师开始点名
讲师传授新技术,同学们讨论很激烈!
======Others的代理工厂,并调用Others的方法
其他人上课不需要点名

  我们发现,这样也可以将增强织入到响应的类的响应方法上。在配置文件中,我写的是方法的全限定名,如果使用正则表达式也是可以匹配多个对应方法的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值