##前言
上一篇文章我们详细介绍了使用动态代理的方式实现AOP,动态代理的方式对于不了解反射机制的小伙伴们可能比较难以理解,Spring对AOP进行了封装,可以使用面向对象的方式来实现AOP。我们这篇文章就来介绍下使用面向对象的方式来实现AOP
##面向对象实现AOP
###实现原理

Spring框架中不需要创建InvocationHandler,只需要创建一个切面对象, 将所有的非业务代码在切面对象中完成即可,Spring 框架底层会自动根据切面类以及目标类生成一个代理对象。就不需要使用动态代理来实现AOP了。
###代码实现
####接我们上一篇文章的代码。
我们先定位切面的位置。

首先,在方法执行之前,我们需要打印方法的参数。

其次,方法返回返回值之前,打印计算结果。

然后,方法执行完成后,输出日志执行完成。

最后,如果方法有异常,则抛出异常。

定位完切面位置,我们需要创建一个类来统一管理非业务代码。

public class ConsoleLog {

}

####根据我们所定位的切面的位置实现日志打印功能。
我们使用注解的方式来实现AOP
在方法执行之前,打印方法的参数

@Aspect
@Component
public class ConsoleLog{
    @Before("execution(public int com.impl.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        //获取方法参数
        String arr = Arrays.toString(joinPoint.getArgs());
        System.out.println(name+"方法的参数是"+arr);
    }
}

代码解析:
@Before表示在方法执行之前去切入。括号中参数表示需要切入类的方法。*是通配符,表示匹配该类中的所有方法。…同样也是通配符,表示匹配该方法的所有参数。
为了获取我们切入的方法的一些信息,我们需要在before方法中传入JoinPoint对象,JoinPoint用于连接我们的目标方法。
然后,我们通过joinPoint的getSignature()方法的getName()获取到目标方法的方法名。
通过joinPoint的getArgs()方法来获取目标方法的参数,从而实现在目标方法执行前打印出方法的参数。
前面我们说过,Spring框架会根据我们的切面类自动生成一个代理对象。但实际上,Spring并不是通过切面类去解析的,而是通过切面类的对象去解析的,所以我们要在ConsoleLog切面类前面加上@Component注解。这个注解的意思就相当于我们在spring.xml文件中配置了一个ConsoleLog切面类的bean,在加这个注解之前,我们需要在pom.xml文件中配置spring-context依赖。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.11.RELEASE</version>
</dependency>

但这样配置完,ConsoleLog还只是个普通对象,我们需要它成为切面对象,在前面的动态代理的方式中是通过实现InvocationHandler接口来成为切面对象的,我们这里直接加上@Aspect注解即可实现。
最后,我们来配置spring.xml文件,我们需要配置的是spring扫描这个类,然后添加注解。

<!--自动扫描包-->
<context:component-scan base-package="com"></context:component-scan>

<!--使Aspect注解生效,为目标类自动生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
复制代码
base-package属性值为所需要扫面包的包名。
注意:我们添加<aop:aspectj-autoproxy></aop:aspectj-autoproxy>之前需要在spring.xml文件中引入aop命名空间。
xmlns:aop="http://www.springframework.org/schema/aop"

获取代理对象并调用

import com.Cal;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App2 {
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
        //获取代理对象
        Cal cal = (Cal) applicationContext.getBean("calImpl");
        cal.add(2,3);
    }
}

日志打印结果:

add方法的参数是[2, 3]

这样我们就完成了我们第一个定位的切面的位置。
方法执行完成后,输出日志执行完成

@After("execution(public int com.impl.CalImpl.*(..))")
public void after(JoinPoint joinPoint){
    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法已执行完成");
}

@After表示在方法执行之后去切入。括号中的参数与前面@Before相同。
日志打印结果:

add方法已执行完成

方法返回返回值之前,打印计算结果

@AfterReturning(value = "execution(public int com.impl.CalImpl.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法执行的结果是"+result);
}

@AfterReturning表示在方法返回返回值之前切入,value属性值为需要切入类的方法,returning属性值为需要获取的返回值,该属性值需要与下面方法参数中Object名相同。
日志打印结果:

add方法执行的结果是5

如果方法有异常,则抛出异常

@AfterThrowing(value = "execution(public int com.impl.CalImpl.*(..))",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception){
    String name = joinPoint.getSignature().getName();
    System.out.println(name+"方法抛出异常:"+exception);
}

@AfterThrowing表示在方法抛出异常时切入,value属性@AfterReturning相同,throwing属性值为所需要抛出的异常名,需要与下面方法中的Exception名对应。
这样我们的切面类就比较完整了,基本实现了我们所需要的日志打印功能。