上篇我们讨论了Spring如何创建切面,相信大家对切面的创建已经非常熟悉了,同时也应该发现了,我们创建的切面通知没有接收任何目标方法的参数,但是在实际的开发中接收目标方法携带的参数应该是非常合理的需求,那么如何接收目标方法的参数以及如何增强目标的对象功能,将是我们这篇讨论的主要话题。

一、接收目标方法参数

1、创建UserService接口

public interface UserService {
    public boolean saveUser(Long id, String userName);
}

2、创建UserService的实现类

public class UserServiceImpl implements UserService {
    @Override
    public boolean saveUser(Long id, String userName) {
        return false;
    }
}

3、创建能接收参数的切面

@Aspect
@Component
public class GetParamsAspect {
    public static Logger LOGGER = LoggerFactory.getLogger(GetParamsAspect.class);
    @Pointcut("execution(* com.icypt.learn.service.UserService.saveUser(Long, String)) && args(userId,userName)")
    public void process(Long userId, String userName) {}
    @Pointcut("execution(* com.icypt.learn.service.UserService.saveUser(..))")
    public void process1() {}
    @Before("process(userId, userName)")
    public void before(Long userId, String userName) {
        LOGGER.info("***********************进入前置通知**********************************");
        LOGGER.info("前置通知接受的userId:" + userId);
        LOGGER.info("前置通知接受的userName:" + userName);
    }
    @After("process1()")
    public void after(JoinPoint jp) {
        LOGGER.info("***********************进入后置通知**********************************");
        //获取目标方法所有的参数
        Object[] args = jp.getArgs();
        for(int i=0; i < args.length; i++) {
            LOGGER.info("第" + i + "个参数的值为:" + args[i]);
        }
        //获取目标方法信息
        Class clazz = jp.getSignature().getDeclaringType();
        LOGGER.info("目标方法执行对象完整类名称:" + clazz.getName());
        LOGGER.info("目标方法执行对简单类名称:" + clazz.getSimpleName());
    }
}

4、编写配置类

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.icypt")
public class JavaConfig {
    @Bean
    public UserService userService() {
        return new UserServiceImpl();
    }
}

5、编写测试类

public class TestUserService {
    public static AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(JavaConfig.class);
    @Test
    public void testSaveUser() {
        UserService userService = acac.getBean("userService", UserService.class);
        //调用目标方法
        userService.saveUser(1l,"daguo");
    }
}

6、运行测试结果

09:03:53,001  INFO main aspect.GetParamsAspect:22 - ***********************进入前置通知**********************************
09:03:53,001  INFO main aspect.GetParamsAspect:23 - 前置通知接受的userId:1
09:03:53,001  INFO main aspect.GetParamsAspect:24 - 前置通知接受的userName:daguo
09:03:53,001  INFO main aspect.GetParamsAspect:29 - ***********************进入后置通知**********************************
09:03:53,001  INFO main aspect.GetParamsAspect:33 - 第0个参数的值为:1
09:03:53,001  INFO main aspect.GetParamsAspect:33 - 第1个参数的值为:daguo
09:03:53,001  INFO main aspect.GetParamsAspect:37 - 目标方法执行对象完整类名称:com.icypt.learn.service.UserService
09:03:53,001  INFO main aspect.GetParamsAspect:38 - 目标方法执行对简单类名称:UserService

7、结果分析

通过运行结果可以发现,前置通知已经能够获取到目标方法指定参数名称的参数值,后置通知也可以通过目标方法的签名信息经反射获取到目标方法以及目标类的相关信息。
需要注意的是:(1)通过指定参数名称去获取参数时,一定要保证切点指示器Args定义的参数名称,切点process定义的参数名称以及通知before定义的参数名称要和目标方法参数名称保持一致,否则无法将参数从切点转移到通知;(2)上述例子分别用前置、后置通知演示了获取参数的两种方式,这两种获参的方式适合于所有的通知,大家不要产生歧义。

二、功能引入

什么是功能引入?什么场景下需要功能引入?假如我们定义了一个公共接口,这个接口有很多实现,但是随着业务的延伸,有些实现类的功能已经不能满足业务的需求需要扩展了,但是并不是所有的实现类都需要扩展这个功能,这时就需要我们对指定的实现类进行功能引入,那么怎么才能在不侵入原代码的情况下引入新的功能呢?答案肯定是通过切面。是的,SpringAOP为我们提供了一种方式,可以让功能的引入变得非常的优雅,下面来看代码。

8、创建引入功能接口以及实现类

public interface EnhanceUserService {
    public boolean update();
}
@Service
public class EnhanceUserServiceImpl implements EnhanceUserService  {
    public static Logger logger = LoggerFactory.getLogger(EnhanceUserServiceImpl.class);
    @Override
    public boolean update() {
        logger.info("************执行增强后新增的update方法****************");
        return false;
    }
}

9、创建引入功能切面

@Aspect
@Component
public class EnhanceAspect {
    @DeclareParents(value = "com.icypt.learn.service.UserService+", defaultImpl = EnhanceUserServiceImpl.class)
    public static EnhanceUserService enhanceUserService;

}

@DeclareParents:value表示的是你要为那个类增加新的功能,这里是所有实现了UserService接口的类;defaultImpl 表示的是你要新增功能的默认实现类是哪一个。

10、编写测试类

public class TestEnhanceUserService {
    public static Logger logger = LoggerFactory.getLogger(TestEnhanceUserService.class);
    public static AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(JavaConfig.class);
    @Test
    public void testSaveUser() {
        UserService userService = acac.getBean("userService", UserService.class);
        //调用目标方法
        boolean flag = userService.saveUser(1l,"daguo");
        logger.info("***********执行目标类saveUser方法结果:" + flag + "**********");
        //调用目标类引入方法
        EnhanceUserService enhanceUserService = (EnhanceUserService) userService;
        enhanceUserService.update();
    }
}

11、运行测试结果

09:49:07,345  INFO main service.TestEnhanceUserService:24 - ***********执行目标类saveUser方法结果:false**********
09:49:07,345  INFO main impl.EnhanceUserServiceImpl:14 - ************执行增强后新增的update方法****************

12、结果分析

我们可以看到在测试类中,我们并没有获取扩展接口的对象,而是将UserService的对象强转为EnhanceUserService 的对象完成了新增方法的调用,间接的实现了UserService实现类的扩展,其实原理很简单,由于aop是基于代理设计实现的,那么在创建UserService实现类的代理对象时,代理类不仅实现了UserService的接口也实现了扩展类的接口,这也就是为什么UserService对象可以强转为EnhanceUserService对象的原因。以上就是SpringAOP接收参数以及功能引入的全部内容,下篇我们以Spring经典的XML配置的方式来完成之前讲解的切面功能。