Spring AOP 面向切面编程

学习日志:资料来自杨开振的《JavaEE 互联网轻量级框架整合开发》

1 一个简单的约定游戏

1.1 约定规则

首先提供一个Interceptor接口:

package com.learn.ssm.chapter11.game;

public interface Interceptor {
    public void before(Object obj);
    
    public void after(Object obj);
    
    public void afterReturning(Object obj);
    
    public void afterThrowing(Object obj);
}

这是一个拦截接口,可以对他创建实现类。
此时,要求生成对象的时候都用这样的一个类去生成相应的对象,如代码:

package com.learn.ssm.chapter11.game;

public class ProxyBeanFactory {
    
    public static <T> T getBeam(T obj, Interceptor interceptor){
        return (T) ProxyBeanUtil.getBean(obj, interceptor);
    }
}

具体类ProxyUtil的getBean方法不需要理会,这是需要去完成的内容,但是使用这个方法后,存在如下约定(这里不讨论ohb对象或者拦截器interceptor为空的情况),只需要做一个简单的判断。
当一个对象通过ProxyBeanFactory的getBean方法定义后,拥有这样的约定:

  • Bean必须是一个实现了某一个接口的对象
  • 最先会执行拦截器的before方法
  • 其次执行Bean的方法(通过反射的形式)
  • 执行Bean方法时,无论是否产生异常,都会执行after方法
  • 执行Bean方法时,如果不产生异常,则执行afterRuning方法;如果产生异常,则执行afterThrowing方法

这个约定已经非常接近Spring AOP对我们的约定。

1.2 读者的代码

上面笔者给出了接口和获取Bean的方式,同时也给出了具体的约定,这个时候读者可以根据约定编写代码,比如打印一个角色信息。由于约定服务对象必须实现接口,于是可以自己定义一个RoleService接口:

package com.learn.ssm.chapter11.game.service;

import com.learn.ssm.pojo.Role;

public interface RoleService {
    public void printRole(Role role);
}

然后就可以编写它的实现类了:

package com.learn.ssm.chapter11.game.service.impl;

import com.learn.ssm.chapter11.game.service.RoleService;
import com.learn.ssm.pojo.Role;

public class RoleServiceImpl implements RoleService {
    @Override
    public void printRole(Role role) {
        System.out.println("{ id = " + role.getId() + ", roleName = " + role.getRoleName()
        + ", note = " + role.getNote() + "}");
    }
}

实现一个拦截器:

package com.learn.ssm.chapter11.game.interceptor;

import com.learn.ssm.chapter11.game.Interceptor;

public class RoleInterceptor implements Interceptor {
    @Override
    public void before(Object obj) {
        System.out.println("准备打印角色信息");
    }

    @Override
    public void after(Object obj) {
        System.out.println("已经完成角色信息的打印处理");
    }

    @Override
    public void afterReturning(Object obj) {
        System.out.println("刚刚完成打印功能,一切正常");
    }

    @Override
    public void afterThrowing(Object obj) {
        System.out.println("打印功能执行异常,检查一下角色对象是否为空");
    }     
}

这个时候你可以清除地知道代码按照流程图的流程执行,注意,你并不需要笔者如何实现,你只需要知道我们之间的约定就好,测试一下:

package com.learn.ssm.chapter11.game.main;

import com.learn.ssm.chapter11.game.Interceptor;
import com.learn.ssm.chapter11.game.ProxyBeanFactory;
import com.learn.ssm.chapter11.game.interceptor.RoleInterceptor;
import com.learn.ssm.chapter11.game.service.RoleService;
import com.learn.ssm.chapter11.game.service.impl.RoleServiceImpl;
import com.learn.ssm.pojo.Role;

public class GameMain {
    public static void main(String[] args){
        RoleService roleService = new RoleServiceImpl();
        Interceptor interceptor = new RoleInterceptor();
        RoleService proxy = ProxyBeanFactory.getBean(roleService, interceptor);
        Role role = new Role(1, "role_name_1","role_note_1");
        proxy.printRole(role);
        role = null;
        proxy.printRole(role);
    }
}

使用者只需要懂得流程图的规定,实现接口中的方法即可,这些都是笔者对你的约定,而你不需要知道笔者是如何实现的。

1.3 笔者的代码

下面通过JDK动态代理实现上述流程的代码:

package com.learn.ssm.chapter11.game;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyBeanUtil implements InvocationHandler {
    
    //被代理对象
    private Object obj;
    
    //拦截器
    private Interceptor interceptor = null;

    /**
     * 获取动态代理对象
     * @param obj
     * @param interceptor
     * @return 动态代理对象
     */
    public static Object getBean(Object obj, Interceptor interceptor){
        //使用当前类,作为代理方法,此时别代理对象执行方法的时候,会进入当前类的invoke方法里
        ProxyBeanUtil _this = new ProxyBeanUtil();
        //保存被代理对象
        _this.obj = obj;
        //保存拦截器
        _this.interceptor = interceptor;
        //生成代理对象,并绑定代理方法
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),_this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        Object retObj = null;
        //是否产生异常
        boolean exceptionFlag = false;
        //before方法
        interceptor.before(obj);
        try{
            //反射原油方法
            retObj = method.invoke(obj,args);
        }catch (Exception ex){
            exceptionFlag = true;
        }finally {
            interceptor.after(obj);
        }
        if(exceptionFlag){
            //afterThrowing方法
            interceptor.afterThrowing(obj);
        }else{
            //afterReturning方法
            interceptor.afterReturning(obj);
        }
        return obj;
    }
}

1.4 小结

image


image

2、术语

  • 切面:
    切面就是在一个怎么样的环境中工作。比如在代码清单中,数据库的事务贯穿了整个代码层面,这就是一个切面,它可以定义后面需要介绍的各类通知、切点和引用等内容,然后Spring AOP会将其定义的内容织入到约定的流程中,在动态代理中理解成一个拦截器,比如RoleInterceptor就是一个切面类。
  • 通知:
    通知是切面开启后,切面的方法。它根据在代理对象真实方法调用前、后的顺序和逻区分,它和约定游戏的例子里的拦截器的方法十分接近。

前置通知(before):在动态代理反射原有对象方法或者执行环绕通知前执行的通知功能。

后置通知(after):在动态代理反射原有对象方法或者执行环绕通知后执行的通知功能。无论是否抛出异常,它都会被执行。

返回通知(afterReturning):在动态代理反射原有对象方法或者执行环绕通知后正常返回(无异常)执行的通知功能。

异常通知(afterThrowing):在动态代理反射原有对象方法或者执行环绕通知产生异常后执行的通知功能。

环绕通知(around):在动态代理中,它可以取代当前被拦截对象的方法,提供回调原有被拦截对象的方法

  • 引入:
    引入允许我们咋现有的类中添加自定义的类和方法

  • 切点:
    这是一个告诉Spring AOP在什么时候启动拦截并织入对应的流程中,因为并不是所有的开发都需要启动AOP的

  • 连接点:
    连接点对应的是具体需要拦截的东西,比如通过切点的正则表达式去判断那些方法是连接点,从而织入对应的通知,比如printRole就是一个连接点。

  • 织入:
    织入是一个生成代理对象并将切面内容放入到流程中的过程。实际代理的方法分为静
    态代理和动态代理。静态代理是在编译class文件时生成的代码逻辑,但是在Spring中并不
    使用这样的方式,所以我们就不展开讨论了。一种是通过ClassLoader也就是在类加载的时
    候生成的代码逻辑,但是它在应用程序代码运行前就生成对应的逻辑。还有一种是运行期,
    动态生成代码的方式,这是Spring AOP所采用的方式, Spring是以JDK和CGLIB动态代
    理来生成代理对象的

3、使用@AspectJ注解开发Spring AOP

2.1 选择连接点

Spring是方法级别的AOP框架,而我们主要也是以某个类的某个方法作为连接点,用动态代理的理论来说,就是要拦截哪个方法,织入对应的AOP通知,为了更好的测试,先建立一个接口,如代码清单11-10:

代码清单11-10:打印角色接口

package com.learn.ssm.chapter11.aop.service;

import com.learn.ssm.chapter10.annotation.pojo.Role;

public interface RoleService {
    public void printRole(Role role);
}

代码清单11-11:RoleService实现类

package com.learn.ssm.chapter11.aop.service.impl;

import com.learn.ssm.chapter10.annotation.pojo.Role;
import com.learn.ssm.chapter11.aop.service.RoleService;

public class RoleServiceImpl implements RoleService {

    @Override
    public void printRole(Role role) {
        System.out.println("{ id = " + role.getId() + ", roleName = " + role.getRoleName()
                + ", note = " + role.getNote() + "}");
    }
}

2.2 创建切面

选择好了连接点就可以使用切面了,对于动态代理的概念而言,它就如同一个拦截器,在Spring中只需要使用@Aspect注解一个类,那么Spring IoC就会认为这是一个切面了.
代码清单11-11:定义切面

package com.learn.ssm.chapter11.aop.aspect;

import org.aspectj.lang.annotation.*;

@Aspect
public class RoleAspect {

    @Before("execution(*com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl.printRole(..))")
    public void before(){
        System.out.println("before...");
    }

    @After("execution(*com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl.printRole(..))")
    public void after(){
        System.out.println("after...");
    }

    @AfterReturning("execution(*com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl.printRole(..))")
    public void afterReturning(){
        System.out.println("afterReturning");
    }

    @AfterThrowing("execution(*com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl.printRole(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
}

3.3 定义切点

代码清单11-11在注解中定义了execution的正则表达式,Spring是通过这个正则表达式判断是否需要拦截你的方法的,这个表达式是:

execution(* com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl.printRole(..))
  • execution:代表执行方法的时候就会触发
  • *代表任意返回类型的方法
  • com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl 类的全限定名
  • printRole 被拦截方法名称
  • (…) 任意的参数
    **表 11-2 AspectJ的指示器
AspectJ指示器描述
arg()限制连接点匹配参数为制定类型的方法
@args()限制而连接点匹配参数为指定注解标注的只是方法
execution用于匹配连接点的执行方法
this()限制连接点匹配AOP代理的Bean,引用为指定类型的类
target限制连接点匹配被代理对象为指定的类型
@target限制连接点匹配特定的执行对象,这些对象要符合指定的注解类型
@within()限制连接点匹配指定的类型
within()限制连接点匹配指定的包
@annotation限定匹配带有指定注解的连接点

比如下面例子,我们只需要对com.learn.ssm.chapter11.aop.impl包及其下面的包的类进行匹配,因此要修改前置通知,这样指示器如下:

代码清单11-13:使用within指示器

@Before("execution(* com.learn.ssm.chapter11.*.*.*.*.printRole(..))" +
            "&& within(com.learn.ssm.chapter11.aop.service.impl.*)")
    public void before(){
        System.out.println("before...");
    }

这里进行了限制,这样Spring只会拿到com.learn.ssm.chapter11.aop.service.impl包下面的类的printRole方法作为连接点。如果使用xml,&& 用 and代替,||用or,!用not
代码清单11-13的正则表达式需要重复书写多次,比较麻烦,只要引入另一个注解@Pointcur定义一个切点就可以避免这个麻烦了

代码清单11-14:使用注解@PoingCut

package com.learn.ssm.chapter11.aop.aspect;

import org.aspectj.lang.annotation.*;

@Aspect
public class RoleAspect {
    
    @Pointcut("execution(* com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl.printRole(..))")
    public void print(){
        
    }
    
    @Pointcut("print()")
    public void before(){
        System.out.println("before...");
    }
    
    @Pointcut("print()")
    public void after(){
        System.out.println("after...");
    }

    @Pointcut("print()")
    public void afterReturning(){
        System.out.println("afterReturning");
    }
    
    @Pointcut("print()")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
}

3.4 测试AOP

代码清单11-15:配置Spring bean

package com.learn.ssm.chapter11.aop.aspect;

import org.aspectj.lang.annotation.*;

@Aspect
public class RoleAspect {

    @Pointcut("execution(* com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl.printRole(..))")
    public void print(){

    }

    @Before("print()")
    public void before(){
        System.out.println("before...");
    }

    @After("print()")
    public void after(){
        System.out.println("after...");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("afterReturning");
    }

    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
}

@EnableAspectJAutoProxy代表启动AspectJ框架的自动代理,这个时候Spring才会生成动态代理对象,进而可以使用AOP,而getRoleAspect方法,则生成一个切面实例。
也可以使用注解的方式:
**代码清单11-16: 使用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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <aop:aspectj-autoproxy/>
    <bean id="roleAspect" class="com.learn.ssm.chapter11.aop.aspect.RoleAspect"/>
    <bean id="roleService" class="com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl"/>
</beans>

其中,aop:aspectj-autoproxy如同注解@EnableAspectJAutoProxy,采用的也是自动代理的功能。

package com.learn.ssm.chapter11.main;

import com.learn.ssm.chapter11.aop.AopConfig;
import com.learn.ssm.chapter11.aop.service.RoleService;
import com.learn.ssm.pojo.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
  public static void main(String[] args){
      ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
      //使用xml
//        ApplicationContext context = new ClassPathXmlApplicationContext("com/learn/ssm/chapter11/aop/spring-config.xml");
      RoleService roleService = (RoleService) context.getBean(RoleService.class);
      Role role = new Role();
      role.setId(1);
      role.setRoleName("role_name_1");
      role.setNote("role_note_1");
      roleService.printRole(role);
      role = null;
      roleService.printRole(role);
  }
}

3.5 环绕通知

环绕通知是Spring AOP中最强大的通知。他可以同时实现前置通知和后置通知。它保留了调度被代理对象原有方法的功能。如果不需要大量改变业务逻辑,一般而言并不需要使用。
代码清单11-18:加入环绕通知

 @Around("print()")
    public void around(ProceedingJoinPoint jp){
        System.out.println("around before ...");
        try{
            jp.proceed();
        }catch (Throwable e){
            e.printStackTrace();
        }
        System.out.println("around after ...");
    }

3.6 织入

织入是生成代理对象并将切面内容放入约定流程的过程,在上述代码中,连接点所在的类都是拥有接口的类,而事实上即使没有接口,Spring也能提供AOP的功能,所以是否拥有接口不是使用Spring AOP的一个强制要求。

3.7 给通知传递参数

在Spring AOP各类通知中,除了环绕通知外,并没有讨论参数的传递,但是有时候我们还是希望传递一点参数的,这里先修改连接点为一个多参数的方法:

package com.learn.ssm.chapter11.aop.service.impl;

import com.learn.ssm.chapter11.aop.service.RoleService;
import com.learn.ssm.pojo.Role;
import org.springframework.stereotype.Component;

@Component
public class RoleServiceImpl implements RoleService {
    @Override
    public void printRole(Role role, int sort) {
        System.out.println("{ id = " + role.getId() + ", roleName = " + role.getRoleName()
                + ", note = " + role.getNote() + "}");
        System.out.println(sort);
    }
}

这里存在两个参数,一个是角色,一个是整形排序参数,那么要把这个方法作为连接点,也就是使用切面拦截这个方法,这里以前置通知为例子:

代码清单 11-19:给通知传递参数

 @Before("print() && args(role,sort)")
    public void before(Role role, int sort)
    {
        System.out.println("before...");
        System.out.println(role);
        System.out.println(sort);
        System.out.println("before end..");
    }

3.8 引入

Spring AOP 只是通过动态代理技术,把各类通知织入到它所约定的流程当中,而事实上,有时候我们希望通过引入其他类的方法来得到更好的实现,这是就可以引入其他的方法了,比如prinRole方法要求,如果要求当角色为空的时候不再打印,那么要引入一个新的检测器对其进行检测,先定义一个RoleVerifier接口:

代码清单11-20:定义RoleVerifier

package com.learn.ssm.chapter11.aop.verifier;

import com.learn.ssm.pojo.Role;

public interface RoleVerifier {
    public boolean verify(Role role);
}

verify方法检测对象role是否为空,如果它不为空才返回true,否则返回false。此时需要一个实现类——RoleVerifierImpl,如代码清单11-21:

**代码清单11-21:RoleVerifierImpl实现RoleVerifier

package com.learn.ssm.chapter11.aop.verifier.impl;

import com.learn.ssm.chapter11.aop.verifier.RoleVerifier;
import com.learn.ssm.pojo.Role;

public class RoleVerifierImpl implements RoleVerifier {
    @Override
    public boolean verify(Role role){
        return role != null;
    }
}

代码清单11-22:加入RoleVerifier到切面中


    @DeclareParents(value = "com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl+",
    defaultImpl = RoleVerifierImpl.class)
    public RoleVerifier roleVerifier;

  • value="“com.learn.ssm.chapter11.aop.service.impl.RoleServiceImpl+” 代表着对RoleServiceImpl进行增强,也就是在RoleServiceImpl中引入一个新的接口
  • defaultImpl:代表其默认的实现类,这里是RoleVerifierImpl
    然后就可以使用这个方法检测了:
    代码清单11-23:使用引入增加检测角色是否为空:
package com.learn.ssm.chapter11.main;

import com.learn.ssm.chapter11.aop.AopConfig;
import com.learn.ssm.chapter11.aop.service.RoleService;
import com.learn.ssm.chapter11.aop.verifier.RoleVerifier;
import com.learn.ssm.pojo.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args){
        ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
        //使用xml
//        ApplicationContext context = new ClassPathXmlApplicationContext("com/learn/ssm/chapter11/aop/spring-config.xml");
        RoleService roleService = (RoleService) context.getBean(RoleService.class);
        RoleVerifier roleVerifier = (RoleVerifier) roleService;
        Role role = new Role();
        role.setId(1);
        role.setRoleName("role_name_1");
        role.setNote("role_note_1");
        if(roleVerifier.verify(role)){
            roleService.printRole(role, 1);
        }
    }
}

使用强制转换之后就可以吧roleService转化为RoleVerifier接口对象,就可以使用verify方法了。

4 使用xml配置开发Spring AOP

先来了解下AOP可配置的元素:

AOP配置元素用途备注
aop:advisor定义AOP的通知器一种较老的方式,目前很少使用
aop:aspect定义一个切面-
aop:before定义前置通知-
aop:after定义后置通知-
aop:around定义环绕通知-
aop:after-returning定义返回通知-
aop:after-throwing定义异常通知-
aop:config顶层的AOP配置元素AOP的配置是以它开始的
aop:pointcur定义切点-

代码清单11-24:定义xml配置AOP拦截的接口

package com.learn.ssm.chapter11.xml.service;

import com.learn.ssm.pojo.Role;

public interface RoleService {
    public void printRole(Role role);
}

代码清单11-25:实现类

package com.learn.ssm.chapter11.xml.service;

import com.learn.ssm.pojo.Role;

public class RoleServiceImpl implements RoleService {

    @Override
    public void printRole(Role role) {
        System.out.println("id = " + role.getId());
        System.out.println("role_name = " + role.getRoleName());
        System.out.println("role_note = " + role.getNote());
    }
}

代码清单11-26:切面类

package com.learn.ssm.chapter11.xml.aspect;

public class XmlAspect {
    
    public void before(){
        System.out.println("before....");
    }
    
    public void after(){
        System.out.println("after....");
    }
    
    public void afterThrowing(){
        System.out.println("after throwing....");
    }
    
    public void afterRetuning(){
        System.out.println("after returning...");
    }
}

没有使用任何注解,这就意味着需要使用xml去想Spring IoC容器描述它们

代码清单11-27:通过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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <bean id="xmlAspect" class="com.learn.ssm.chapter11.xml.aspect.XmlAspect"/>
    <bean id="roleService" class="com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl"/>
    <aop:config>
        <!-- 引用xmlAspect作为切面 -->
        <aop:aspect ref="xmlAspect">
            <!--定义通知-->
            <aop:before method="before"
                        pointcut="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..))"/>
            <aop:after method="after"
                        pointcut="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..))"/>
            <aop:after-throwing method="afterThrowing"
                        pointcut="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..))"/>
            <aop:after-returning method="afterReturning"
                        pointcut="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

这里首先通过引入xml定义了AOP的命名空间,然后定义了一个roleService类和切面xmlAspect类。
代码清单11-28:定义切点并引入

<?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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="xmlAspect" class="com.learn.ssm.chapter11.xml.aspect.XmlAspect"/>
    <bean id="roleService" class="com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl"/>
    <aop:config>
        <!-- 引用xmlAspect作为切面 -->
        <aop:aspect ref="xmlAspect">
            <aop:pointcut id="printRole" expression="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..))"/>
            <!--定义通知-->
            <aop:before method="before"
                        pointcut-ref="printRole"/>
            <aop:after method="after"
                        pointcut="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..))"/>
            <aop:after-throwing method="afterThrowing"
                        pointcut="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..))"/>
            <aop:after-returning method="afterReturning"
                        pointcut="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

4.2 环绕通知

代码清单11-29:加入环绕通知

 public void around(ProceedingJoinPoint jp){
        System.out.println("around before...");
        try{
            jp.proceed();
        }catch (Throwable e){
            new RuntimeException("回调原有流程,产生异常")
        }
        System.out.println("around after");
    }
   <aop:around method="around"
                        pointcut-ref="printRole"/>

4.3 给通知传递参数

            <aop:before method="before"
                        pointcut="execution(* com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl.printRole(..)) and args(role)"/>

4.4 引入

package com.learn.ssm.chapter11.xml.aspect;

import com.learn.ssm.chapter11.aop.verifier.RoleVerifier;
import com.learn.ssm.pojo.Role;
import org.aspectj.lang.ProceedingJoinPoint;

public class XmlAspect {


    public RoleVerifier roleVerifier = null;

    public void before(Role role){
        System.out.println("before....");
    }

    public void after(){
        System.out.println("after....");
    }

    public void afterThrowing(){
        System.out.println("after throwing....");
    }

    public void afterReturning(){
        System.out.println("after returning...");
    }

    public void around(ProceedingJoinPoint jp){
        System.out.println("around before...");
        try{
            jp.proceed();
        }catch (Throwable e){
            new RuntimeException("回调原有流程,产生异常");
        }
        System.out.println("around after");
    }
}
<aop:declare-parents types-matching="com.learn.ssm.chapter11.xml.service.impl.RoleServiceImpl"
                                 implement-interface="com.learn.ssm.chapter11.aop.verifier.RoleVerifier"/>

5、经典的Spring AOP应用程序

代码清单11-32 :定义ProxyFactoryBeanAspect类

package com.learn.ssm.chapter11.aspect;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class ProxyFactoryBeanAspect implements MethodBeforeAdvice {

    /**
     * 
     * @param method 被拦截方法(连接点)
     * @param params 参数 数组【role]
     * @param roleService 被拦截对象
     */
    @Override
    public void before(Method method, Object[] params, Object roleService){
        System.out.println("前置通知!!!");
    }
}

代码清单11-33:使用xml描述ProxyFactoryBean生成代理对象

<?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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="proxyFactoryBeanAspect" class="com.learn.ssm.chapter11.aspect.ProxyFactoryBeanAspect"/>

    <!-- 设定代理类 -->
    <bean id="roleService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 这里代理的是接口 -->
        <property name="proxyInterfaces">
            <value>com.learn.ssm.chapter11.game.service.RoleService</value>
        </property>
        <!-- 是ProxyFactoryBean 要代理的的目标类 -->
        <property name="target">
            <bean class="com.learn.ssm.chapter11.game.service.impl.RoleServiceImpl"/>
        </property>
        
        <!-- 定义通知 -->
        <property name="interceptorNames">
            <list>
                <value>proxyFactoryBeanAspect</value>
            </list>
        </property>
    </bean>
</beans>

6、多个切面

上面例子讨论了一个方法只有一个切面的问题,而事实上Spring也能支持多个切面。当有多个切面时,在测试过程中发现它不会存在任何顺序,这些顺序代码会随机生成,但是我们有时候希望顺序运行。

代码清单11-35:定义多个切面的切面方法:

package com.learn.ssm.chapter11.multi.bean;

public interface MultiBean {
    public void testMulti();
}

代码清单11-36:实现MultiBean接口

package com.learn.ssm.chapter11.multi.bean.impl;

import com.learn.ssm.chapter11.multi.bean.MultiBean;

public class MultiBeanImpl implements MultiBean {

    @Override
    public void testMulti() {
        System.out.println("test multi aspect!!");
    }
}

这样就定义好了连接点,那么现在需要切面,Aspect1,Aspect2,和Aspect3进行AOP变成,这3个切面的定义,如下:

package com.learn.ssm.chapter11.multi.aspect;


import org.aspectj.lang.annotation.*;

@Aspect
public class Aspect3 {

    @Pointcut("execution(* com.learn.ssm.chapter11.multi.bean.impl.MultiBeanImpl.testMulti(..))")
    public void print(){
    }


    @Before("print()")
    public void before(){
        System.out.println("before 3.......");
    }

    @After("print()")
    public void after(){
        System.out.println("after 3.....");
    }

    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("after throwing 3...");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("after returning 3...");
    }
}

package com.learn.ssm.chapter11.multi.aspect;


import org.aspectj.lang.annotation.*;

@Aspect
public class Aspect2 {

    @Pointcut("execution(* com.learn.ssm.chapter11.multi.bean.impl.MultiBeanImpl.testMulti(..))")
    public void print(){
    }


    @Before("print()")
    public void before(){
        System.out.println("before 2.......");
    }

    @After("print()")
    public void after(){
        System.out.println("after 2.....");
    }

    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("after throwing 2...");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("after returning 2...");
    }
}


package com.learn.ssm.chapter11.multi.aspect;


import org.aspectj.lang.annotation.*;

@Aspect
public class Aspect1 {

    @Pointcut("execution(* com.learn.ssm.chapter11.multi.bean.impl.MultiBeanImpl.testMulti(..))")
    public void print(){
    }


    @Before("print()")
    public void before(){
        System.out.println("before 1.......");
    }

    @After("print()")
    public void after(){
        System.out.println("after 1.....");
    }

    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("after throwing 1...");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("after returning 1...");
    }
}


这样3个切面就拦截了这个连接点,那么执行顺序是怎样?运行看看。

package com.learn.ssm.chapter11.multi.config;

import com.learn.ssm.chapter11.multi.aspect.Aspect1;
import com.learn.ssm.chapter11.multi.aspect.Aspect2;
import com.learn.ssm.chapter11.multi.aspect.Aspect3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.learn.ssm.chapter11.multi")
public class MultiConfig {

    @Bean
    public Aspect1 getAspect1(){
        return new Aspect1();
    }

    @Bean
    public Aspect2 getAspect2(){
        return new Aspect2();
    }

    @Bean
    public Aspect3 getAspect3(){
        return new Aspect3();
    }
}
package com.learn.ssm.chapter11.main;

import com.learn.ssm.chapter11.multi.bean.MultiBean;
import com.learn.ssm.chapter11.multi.config.MultiConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args){
        ApplicationContext context = new AnnotationConfigApplicationContext(MultiConfig.class);
        MultiBean multiBean = context.getBean(MultiBean.class);
        multiBean.testMulti();
    }
}

得到如下:

before 1.......
before 2.......
before 3.......
test multi aspect!!
after 3.....
after returning 3...
after 2.....
after returning 2...
after 1.....
after returning 1...

显然,多个切面是无序的,其执行顺序值得我们探讨,可以给切面加入@Ordered,:

package com.learn.ssm.chapter11.multi.aspect;


import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;

@Aspect
@Order(1)
public class Aspect1 {

    @Pointcut("execution(* com.learn.ssm.chapter11.multi.bean.impl.MultiBeanImpl.testMulti(..))")
    public void print(){
    }

    @Before("print()")
    public void before(){
        System.out.println("before 1.......");
    }

    @After("print()")
    public void after(){
        System.out.println("after 1.....");
    }

    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("after throwing 1...");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("after returning 1...");
    }
}

分别在另外两个加入Order(2)…,就能像是责任链模式这样执行。
xml:

        <aop:aspect ref="xmlAspect" order="1"/>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值