Spring切面简介

1 切点简介

代码下载

增强提供了连接点信息(1.执行点即什么情况或者方法中织入增强,2.方位信息即在连接点执行前,执行后...)和织入逻辑。此时的增强适用于目标类的每个方法。必须要结合切点进行过滤才能将增强用于特定连接点,也就是说切点制定了过滤规则用来筛选连接点。如下是Pointcut的接口定义以及依赖关系


Pointcut由ClassFilter和MethodMather组成。从类命的定义就可以知道。ClassFilter负责定位特定的类,其中matches(Class clazz)中的参数就是需要检测的类。MethodMatcher负责定位特定类的类定方法。该类提供了重载方法。我们可以从多个层面筛选方法(eg:方法名,方法名+参数)。

Spring支持两种方法匹配:

1.当isRuntime()返回false时,属于静态匹配模式,该模式下仅仅对方法签名(方法名称,参数类型,参数类型顺序)进行匹配。方法签名是固定的所以静态匹配只匹配一次。

2.当isRuntime()返回true时,属于动态匹配模式,该模式会在运行期检查方法入参的值,因为调用方法时,传入的参数值都不一样,所以只要发生了方法调用都会进行动态匹配。由此可见,动态匹配很影响性能。

2 切点类型

2.1 静态方法切点

StaticMethodMatcherPointcut是静态方法切点的抽象类,默认匹配目标类的所有方法。

该类有两个子类

1.NameMatchMethodPointcut:采用简单的字符串匹配方式来匹配方法签名

2.AbstractRegexpMethodPointcut:采用正则表达式的方式来匹配方法签名

2.2 动态方法切点

DynamicMethodMatcherPointcut是动态方法切点的抽象了,默认匹配目标类的所有方法

2.3 注解切点

AnnotationMatchingPointcut是注解切点的默认实现类。主要支持以注解方式定义的切点

2.4 表达式切点

ExpressionPointcut是表达式切点的接口,主要是支持AspectJ表达式定义的切点

2.5 流程切点

ControlFlowPointcut是流程切点的实现类。通过查找目标方法是否由某一方法直接或者间接发起调用。从而判断是否为匹配的连接点

2.6 复合切点

ComposablePointcut是复合切点的实现类。通俗的来讲就是对多个切点进行运算,从而获得新的切点

3 切面简介

增强包含了织入逻辑以及连接点信息,切点定义了具体的过滤规则。将二者结合才能制作切面,也就说说切面是增强和切点的结合

3.1 切面继承体系


3.2 切面分类

3.2.1 Advisor

普通切面。一个增强就是一个切面。因为增强包含了织入逻辑,连接点。但是此时的切面太宽泛。

3.2.2 PointcutAdvisor

具有切点的切面,包含了增强和切点两个类。通过切点过滤,可以将增强织入到对应的连接点中


3.2.3 IntroductionAdvisor

该切面是对应引介增强的特殊切面。用于类层面,所以该切面采用ClassFilter进行定义

3.3 切面类型

3.3.1 DefaultPointcutAdvisor

最常用的切面类,一般通过任意的Pointcut和Advice定义该类型的切面,但是不支持引介切面。通过继承该类扩展切面

3.3.2 RegexpMethodPointcutAdvisor

以正则表达式字符串定义的切面

3.3.3 StaticMethodMatcherPointcutAdvisor

静态方法匹配器切点定义的切面

3.3.4 AspectJExpressionPointcutAdvisor

AspectJ表达式定义的切点切面

3.3.5 AspectJPointcutAdvisor

AspectJ语法定义的切点切面

4 切面实现示例

采用环绕增强模拟事务提交或回滚

4.1 StaticMethodMatcherPointcutAdvisor示例实现

该种模式下有多种实现一种是采用自己定义的切面实现,一种是采用系统提供的切面类实现

切面是切点和增强的结合,也就说创建切面一般需要创建增强和切点并在切点中定义具体的过滤规则

采用环绕增强来实现数据库事务的提交

示例工具类

public class Utils {
    public static boolean matches(Method method, Class<?> clazz) {
        Method[] methods = clazz.getDeclaredMethods();
        List<String> methodNames = new ArrayList<String>();
        for (Method singleMethod : methods) {
            methodNames.add(singleMethod.getName());
        }
        return methodNames.contains(method.getName());
    }

    public static ClassFilter getClassFilter() {
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                return IUserDao.class.isAssignableFrom(clazz);
            }
        };
    }
}
public interface IUserDao {
    public void insertUser(User user) throws Exception;
    public void deleteUser(int id) throws Exception;
    public List<User> queryAllUser() throws Exception;
}
public class UserDao implements IUserDao {
    @Override
    public void insertUser(User user) throws Exception {
        throw new Exception();
    }

    @Override
    public void deleteUser(int id) throws Exception {

    }

    @Override
    public List<User> queryAllUser() throws Exception {
        return Collections.EMPTY_LIST;
    }
}

4.1.1 采用自定义切面

public class UserDaoStaticNameMethodAdvisor extends StaticMethodMatcherPointcutAdvisor {
    @Override
    public boolean matches(Method method, Class<?> clazz) {
        return method.getName().equals("queryAllUser");//Utils.matches(method, clazz);
        //前者只增强queryAllUser后者增强所有的方法
    }

    @Override
    public ClassFilter getClassFilter() {
        return Utils.getClassFilter();
    }
}
//自定义增强
public class UserDaoAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        String methodName = methodInvocation.getMethod().getName();
        System.out.println("启动事务开始执行" + methodName);
        Object retVal = null;
        try {
            retVal = methodInvocation.proceed();
        } catch (Exception e) {
            System.out.println("执行" + methodName + "失败,回滚事务");
            throw new Exception("执行" + methodName + "失败");
        }
        System.out.println("执行" + methodName + "成功,提交事务");
        return retVal;
    }
}
        Advice advice = new UserDaoAdvice();
        Pointcut pointcut = new StaticMethodNamePointcut();

        StaticMethodMatcherPointcutAdvisor advisor = new UserDaoStaticNameMethodAdvisor();
        advisor.setAdvice(advice);

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisor(advisor);

        proxyFactory.setTarget(new UserDao());
        proxyFactory.setInterfaces(IUserDao.class);

        try {
            ((IUserDao) proxyFactory.getProxy()).deleteUser(5);
            ((IUserDao) proxyFactory.getProxy()).queryAllUser();
            ((IUserDao) proxyFactory.getProxy()).insertUser(new User());
        }catch (Exception e) {
            System.out.println(e.getMessage());
        }

输出

启动事务开始执行deleteUser
执行deleteUser成功,提交事务
启动事务开始执行queryAllUser
执行queryAllUser成功,提交事务
启动事务开始执行insertUser
执行insertUser失败,回滚事务
执行insertUser失败

配置如下

    <bean id="advice" class="com.demo.advice.UserDaoAdvice"></bean>
    <bean id="target" class="com.demo.impl.UserDao"></bean>
    <bean id="advisor" class="com.demo.advisor.UserDaoStaticNameMethodAdvisor">
        <property name="advice" ref="advice"></property>
    </bean>

    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="target"></property>
        <property name="interceptorNames" value="advisor"></property>
        <property name="interfaces" value="com.demo.inter.IUserDao"></property>
    </bean>

4.1.2 采用系统提供的切面

该种模式下需要在切点中定义过滤规则

//自定静态方法切点
public class StaticMethodNamePointcut extends StaticMethodMatcherPointcut {

    /**
     * 在方法层面进行筛选
     * @param method 目标方法
     * @param clazz 筛选类型
     * @return
     */
    @Override
    public boolean matches(Method method, Class<?> clazz) {
        return Utils.matches(method, clazz);
    }

    /**
     * 在类层面进行筛选
     * @return
     */
    @Override
    public ClassFilter getClassFilter() {
        return Utils.getClassFilter();
    }
}
        Advice advice = new UserDaoAdvice();
        Pointcut pointcut = new StaticMethodNamePointcut();

        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setAdvice(advice);
        advisor.setPointcut(pointcut);

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisor(advisor);

        proxyFactory.setTarget(new UserDao());
        proxyFactory.setInterfaces(IUserDao.class);

        try {
            ((IUserDao) proxyFactory.getProxy()).deleteUser(5);
            ((IUserDao) proxyFactory.getProxy()).queryAllUser();
            ((IUserDao) proxyFactory.getProxy()).insertUser(new User());
        }catch (Exception e) {
            System.out.println(e.getMessage());
        }

配置

    <bean id="advice" class="com.demo.advice.UserDaoAdvice"></bean>
    <bean id="target" class="com.demo.impl.UserDao"></bean>
    <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="advice"></property>
    </bean>

    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="target"></property>
        <property name="interceptorNames" value="advisor"></property>
        <property name="interfaces" value="com.demo.inter.IUserDao"></property>
    </bean>

通过上面的示例可以看出,不管是采用自定切面还是默认切面,都是将增强,切点和切面关联。比如将切点和增强设置到切面中或者将增强设置到切面中。

4.2 RegexpMethodPointcutAdvisor示例

RegexpMethodPointcutAdvisor正则表达式匹配的切面。也就是说可以通过正则表达式实现一个匹配模式。只要符合该模式的方法都需要自如增强。

    <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="advice"></property>
        <property name="patterns">
            <!--将增强织入query开始的放到中-->
            <list><value>.*query.*</value></list>
        </property>
    </bean>

如果需要对所有的方法增强采用.*.*,如果只对以某个字符串开始的方法增强采用*.*[patterns].*

4.3 动态切面示例

方法一旦定义好之后,方法签名一般情况下是不会改变的,方法签名信息属于静态信息,能够改变的

只有形参的值,属于动态信息,只能通过判断参数值的方式达到动态切面的效果。

在spring中没有提供动态切面的接口,但是可以采用实现DynamicMethodMatcherPointcut的方式

实现动态切点。之后将动态切点后切面整合就可以达到动态切面的效果。

DynamicMethodMatcherPointcut处理含有静态检测外,还含有动态检测。静态检测主要检测类型以及方法名称是否匹配。动态检测主要检测的是参数值是否存在约束。采用动静结合的方式提升效率

public class DynamicMethodNamePointcut extends DynamicMethodMatcherPointcut {

    //参数值约束
    private static List<Integer> notAllowDeleteIDs = new ArrayList<Integer>(2);

    static {
        notAllowDeleteIDs.add(1);
        notAllowDeleteIDs.add(2);
    }

    //类型属于静态信息,所以该处属于静态检测
    @Override
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                return IUserDao.class.isAssignableFrom(clazz);
            }
        };
    }

    //通过method对象获得参数类型/个数,方法名,类型都属于静态信息,所以该处属于静态检测
    @Override
    public boolean matches(Method method, Class<?> clazz) {
        return method.getName().endsWith("deleteUser");
    }

    @Override
    //关键就是第三个参数,代表的含义是方法入参的值,每次调用时传入的值都不一样,
    //所以需要在每次调用是做检测,所以该处属于动态检测
    //做动态切面该方法是必须要实现的
    public boolean matches(Method method, Class<?> clazz, Object... args) {
        System.out.println("方法名称: " + method.getName());
        int id = (Integer) args[0];
        if (notAllowDeleteIDs.contains(id)) {
            System.out.println("不允许删除的ID: " + id);
            return false;
        }
        return true;
    }
}
    <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="advice"></property>
        <property name="pointcut">
            <bean class="com.demo.pointcut.DynamicMethodNamePointcut"></bean>
        </property>
    </bean>

4.4 流程切面示例

ControlFlowPointcut是构成流程切面的基石,在某些情况下,不会直接调用目标方法而是有某个方法直接或者间接发起调用目标方法,此时也希望目标方法能够得到增强。此时就可以使用该中切面

public class UserDaoWrapper {

    private IUserDao userDao;

    public void setUserDao(IUserDao userDao) {
        this.userDao = userDao;
    }

    //通过该方法调用目标方法
    public List<User> queryUser(String userName) throws Exception {
        try {
            if (userName.isEmpty()) {
                return userDao.queryAllUser();
            } else {
                return userDao.queryUserByName(userName);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return Collections.emptyList();
    }
}
    <bean id="pointcut" class="org.springframework.aop.support.ControlFlowPointcut">
        <constructor-arg type="java.lang.Class" value="com.demo.wrapper.UserDaoWrapper"></constructor-arg>
        <constructor-arg type="java.lang.String" value="queryUser"></constructor-arg>
    </bean>

    <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="advice"></property>
        <property name="pointcut" ref="pointcut"></property>
    </bean>

    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="target"></property>
        <property name="interceptorNames" value="advisor"></property>
        <property name="optimize" value="true"></property>
        <property name="interfaces" value="com.demo.inter.IUserDao"></property>
        <property name="proxyTargetClass" value="true"></property>
    </bean>

ControlFlowPointcut有两个构造方法:

ControlFlowPointcut(Class<?> clazz) 指定一个类作为流程切点

ControlFlowPointcut(Class<?> clazz, String methodName) 指定一个类和某一个方法作为流程切点

流程切面和动态切面都属于一类切面,即在运行时检测环境.前者检测目标方法是否有配置中的方法发起调用,后者检测参数值是否符合约束

4.5 复合切面示例

ComposablePointcut是构成复合切面的基石,提供对两个切点的交并运算,切点和ClassFilter/MethodMatcher的交并运算。通俗的讲就是对切点进行运算。复合切点只能通过代码的方式构建,无法通过xml构建复合切面,只能通过代码的方式构建复合切点然后通过xml或者代码的方式注入到复合切面中

public class CreateComposablePointcut {


    public Pointcut getComposablePointcut() {
        ComposablePointcut composablePointcut = new ComposablePointcut();

        Pointcut controlFlowPointcut = new ControlFlowPointcut(UserDaoWrapper.class, "queryUser");

        Pointcut dynamicMethodNamePointcut = new DynamicMethodNamePointcut();

        return composablePointcut.intersection(controlFlowPointcut).union(dynamicMethodNamePointcut);
    }
}
    <bean id="composablePointcutService" class="com.demo.pointcut.CreateComposablePointcut"></bean>
    <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="advice"></property>
        <property name="pointcut" value="#{composablePointcutService.composablePointcut}"></property>
    </bean>

    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="target"></property>
        <property name="interceptorNames" value="advisor"></property>
        <property name="optimize" value="true"></property>
        <property name="interfaces" value="com.demo.inter.IUserDao"></property>
        <property name="proxyTargetClass" value="true"></property>
    </bean>

4.6 引介切面示例

引介增强是引介切面的基础。


DeclareParentsAdvisor用于实现AspectJ语言的DeclareParent注解表示的引介切面,

DefaultIntroductionAdvisor是引介切面最常用的类,主要有三个构造方法

//通过增强创建引介切面,引介切面为目标类实现增强对象的所有接口
public DefaultIntroductionAdvisor(Advice advice) {}

//通过增强创建引介切面,引介切面为目标类实现的接口是由IntroductionInfo#getInterfaces()决定的
public DefaultIntroductionAdvisor(Advice advice, IntroductionInfo introductionInfo) {}

//通过增强创建引介切面,引介切面为目标类实现的接口是由Class类型决定的,即该接口和Class类型一致
public DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice, Class<?> intf) {}
public class UserDaoIntroductionAdvice extends DelegatingIntroductionInterceptor implements PerformanceMointor {
    private ThreadLocal<Boolean> isActive = new ThreadLocal<Boolean>();

    @Override
    public void setActive(boolean isActive) {
        this.isActive.set(isActive);
    }

    private boolean getActive() {
        return this.isActive.get() != null && this.isActive.get() ? true : false;
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        String methodName = mi.getMethod().getName();
        if (getActive()) System.out.println("启动事务开始执行" + methodName);
        Object retVal = null;
        try {
            retVal = super.invoke(mi);
        } catch (Exception e) {
            if (getActive()) System.out.println("执行" + methodName + "失败,回滚事务");
            throw new Exception("执行" + methodName + "失败");
        }
        if (getActive()) System.out.println("执行" + methodName + "成功,提交事务");
        return retVal;
    }
}
public interface PerformanceMointor {

    public void setActive(boolean isActive);
}
<!--目标对象-->
    <bean id="userDaoTarget" class="com.demo.impl.UserDao"></bean>
    <!--引介增强实现类-->
    <bean id="introductionAdvice" class="com.demo.advice.UserDaoIntroductionAdvice"></bean>
    <!--ProxyFactory-->
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--采用生成子类方式实施增强,底层采用的CGLib,所以此处设置为ture-->
        <property name="proxyTargetClass" value="true"></property>
        <property name="targetName" value="userDaoTarget"></property>
        <!--配置引介增强-->
       <property name="interceptorNames" value="introductionAdvice"></property>
        <!--实现的接口-->
        <property name="proxyInterfaces | interfaces">
            <list>
                <value>com.demo.inter.PerformanceMointor</value>
                <value>com.demo.inter.IUserDao</value>
            </list>
        </property>
    </bean>

5 总结

不管是何种切面,都能找到与之相对应的切点,由此可以看出切点是实现切面的基础

创建切面的基本步骤如下

1.创建增强

2.根据需要创建切点,切面以及增强

3.通过setter的方式将切点和增强注入切面

4.采用ProxyFactoryBean生成动态代理

6 自动创建代理

以上的方法都是虽然可以实现将切面织入到目标类中,但是配置较多,Spring提供了自动生成代理的功能来减少配置,自动生成代理类都是基于BeanPostFactory的自动代理创建器的实现类,分为如下几类

1.基于bean配置名规则的自动代理创建器:允许一组特定配置名的bean自动创建代理实例的代理创建器。实现类BeanNameAutoProxyCreator

2.基于Advisor匹配机制的自动代理创建器:它会对容器中所有的advisor进行扫描,自动讲切面应用到目标Bean中即为目标Bean创建实例。实现类DefaultAdvisorAutoProxyCreator

3.基于Bean中AspectJ注解标签的自动代理创建器:为包含AspectJ注解的Bean自动创建代理实例。实现类AnnotationAwareAspectJProxyCreator


自动代理创建器类都实现了BeanPostProcessor,根据Bean的生命周期知道此时可以获得操作Bean的机会,也就是在此时自动代理创建器有机会对匹配规则的Bean自动创建代理对象

6.1 BeanNameAutoProxyCreator实例

假设我们需要讲上面的环绕增强织入UserDao,OrderDao,VIPDao中以便达到事务控制的目的。采用传统的方式需要配置多次,才能实现该功能,如果采用BeanNameAutoProxyCreator配置就少很多

    <bean id="target1" class="com.demo.impl.UserDao"></bean>
    <bean id="target2" class="com.demo.impl.OrderDao"></bean>
    <bean id="target3" class="com.demo.impl.VIPDao"></bean>
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="interceptorNames" value="advice"></property>
        <property name="beanNames" value="target*"></property>
    </bean>
<property name="beanNames" value="target*"></property>

表示以target开始的Bean。也就是所会将增强织入到容器中所有以target字符开始定义的Bean中,但是并不是所有以target字符开始定义的Bean都需要织入该增强。可以采用如下的方式

<property name="beanNames" value="target1,target2"></property>
直接指定了增强需要织入的目标Bean,前者采用的是正则匹配的方式,后者直接指定了织入的Bean

6.2 DefaultAdvisorAutoProxyCreator示例

 <bean id="regexAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="advice"></property>
        <property name="patterns">
            <!--将增强织入query开始的放到中-->
            <list><value>.*query.*</value></list>
        </property>
    </bean>
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

表示将增强织入容器中所有带query方法名的目标Bean中,也就说目标类中含有方法query。


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值