AOP

做代理的目的就是在不影响原有代码业务逻辑的情况下,在业务前后加入相关处理,比如参数验证和日志。在这里的日志我们称为切面,所以AOP有称作面向切面的编程。

spring原生API实现的AOP
使用前一节讲述的动态代理(invoke)的方法其实也是可以轻松实现AOP的功能的,本节使用spring原生的API做AOP
引入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
服务类
- 用户服务接口
package service;
public interface UserService {
//增加用户
public void add();
//删除用户
public void delete();
//更新用户
public void update();
//用户查询
public void select();
}
- 接口实现类
package service;
public class UserServiceImpl {
public void add(){
System.out.println("增加用户");
}
public void delete(){
System.out.println("删除用户");
}
public void update(){
System.out.println("更新用户");
}
public void select(){
System.out.println("查询用户");
}
}
我们需要在调用用户服务的任意方法时都做前置日志与后置日志的打印
日志类
- 前置日志
package Log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"要被执行了");
}
}
- 后置日志*
package Log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为"+returnValue);
}
}
下面我们使用spring的方法去做,也就是注册bean和配置aop,在resource下编写application.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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="UserService" class="service.UserServiceImpl"/>
<bean id="log" class="Log.Log"/>
<bean id="AfterLog" class="Log.AfterLog"/>
<!--使用原生springAPI接口-->
<!-- 配置Aop,需要导入Aop约束-->
<aop:config>
<!-- 切入点-->
<aop:pointcut id="pointcut" expression="execution(* service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="AfterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>

切入点表示在哪里执行切面,也就是前后日志。上述配置表示在执行service.UserServiceImpl的任意方法,传入任意参数时,加入切面。

搭配上切入点,就是在执行UserServiceImpl的任意函数,且不管传入什么参数时去执行前后日志。
测试
@Test
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
//需要获得接口,而不是实现类
UserService userService = (UserService)context.getBean("UserService");
userService.add();
}

自定义API实现AOP
上一节使用spring的原生接口实现AOP,我们也可以自己定义一些接口,在xml文件中做好AOP的配置,实现自定义的AOP。、
自定义接口
package diy;
public class diyPointCut {
public void before(){
System.out.println("==========方法执行前");
}
public void after(){
System.out.println("==========方法执行后");
}
}
接口很简单,但是仔细对比一下就会发现,自定义的API不能像原生API那样获取对象类和方法的一些属性,不过一般在仅做代理的情况下是没有问题的。
xml配置文件
<!-- 注册bean-->
<bean id="diy" class="diy.diyPointCut"/>
<aop:config>
<!-- 自定义切面 ref表示要引用的类-->
<aop:aspect ref="diy">
<!-- 切入点-->
<aop:pointcut id="diypoint" expression="execution(* service.UserServiceImpl.*(..))"/>
<!-- 通知-->
<aop:before method="before" pointcut-ref="diypoint"/>
<aop:after method="after" pointcut-ref="diypoint"/>
</aop:aspect>
</aop:config>
<aop:aspect ref="diy">表示自定义切面,原生API是不需要的。
<aop:before method="before" pointcut-ref="diypoint"/>表示在切入点之前执行的方法,把before换成after就是在切入点之后执行的方法。
因为原生API在方法中就定义好了是在切入点之前还是之后执行,所以不需要写这两句。
测试结果
测试程序和第一节的一样,结果如下:

注解实现AOP
注解实现AOP和自定义有一些相似,只不过将配置项都通过注解的方式写在了类里面罢了。
在配置文件中需要注册切面类并打开注解配置项。
xml配置文件
<!-- 方式三,注解-->
<bean id="annotation" class="Annotation.AnnotationPointCut"/>
<!-- JDK(默认 proxy-target-class="false") cglib(proxy-target-class="true")-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
切面类
package Annotation;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect //表示该类为切面
public class AnnotationPointCut {
//切入点之前执行
@Before("execution(* service.UserServiceImpl.*(..))")
public void before(){
System.out.println("==========方法执行前");
}
//切入点之后执行
@After("execution(* service.UserServiceImpl.*(..))")
public void after(){
System.out.println("==========方法执行后");
}
}
测试结果

在注解中,处理上面的方法需要理解之外,还有一个特殊的注解方法叫环绕增强,需要理解,在环绕增强中,可以给定一个参数,代表我们要获取的切入点,换句话说,在该方法中,我们能够获取切入点的类和方法信息,这个有点像
spring的原生API。我们看一下代码。
环绕
//环绕,在环绕增强中,我们可以给定一个参数,代表我们要获取的切入点
@Around("execution(* service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("---------环绕前");
//执行方法,代表切入点方法执行,用于确定切入点执行的位置。
Object proceed = jp.proceed();
System.out.println("---------环绕后");
}
上述代码表示在切入点方法执行之前先打印"---------环绕前",执行完之后在打印"---------环绕后"
结果

如果Before,After,Around,涉谁先谁后呢,上面的代码都不变直接看结果:

Before,After相对于Around更内层一点,伴随jp.proceed()一起执行的应该是。
扩展
扩展部分了解即可,上面说过Around可以获得切入点的类与方法属性,比如
jp.getSignature() 可以获得类和方法签名
