通知(Advice): 除了目标方法以外的操作都称之为通知
切入点(PointCut): 要为哪些类中的哪些方法加入通知
切面(Aspect): 通知 + 切入点

AOP同样是用动态代理来实现的,能让被代理的类更专注的做自己的事情,其他事情交给代理来做就好了,比如事务,性能测试
当然spring中的AOP多种多样,有前置通知,后置通知,环绕通知(重点)以及异常通知,下面就来实现一下吧,首先实现AOP需要导入spring的jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
1.前置通知,需要创建一个advice通知类,并实现MethodBeforeAdvice接口
MyBeforeAdvice类
//自定义记录业务方法名称前置通知 前置通知:目标方法执行之前先执行的额外操作
public class MyBeforeAdvice implements MethodBeforeAdvice {
//before 参数1:当前执行方法对象 //参数2:当前执行方法的参数 //参数3:目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("==============进入前置通知==============");
//System.out.println("当前执行方法: "+method.getName());
//System.out.println("当前执行方法参数: "+args[0]);
//System.out.println("目标对象: "+target);
System.out.println("当前执行方法名称: "+method.getName());
//System.out.println("========================================");
}
}
这样通知advice就已经完成,这个方法有三个参数可以使用,可以获取到这个方法的具体信息,方便我们的动态代理增强操作,下面就来配置一下切入点吧
spring配置文件spring.xml
<!--注册通知-->
<bean id="myBeforeAdvice" class="aop.MyBeforeAdvice"/>
<!--组装切面-->
<aop:config>
<!--配置切入点pointcut
id:切入点在工厂中唯一标识
expression: 用来指定切入项目中哪些组件中哪些方法
execution(返回值 包.类名.*(..))
-->
<aop:pointcut id="pc" expression="execution(* aop.*ServiceImpl.*(..))"/>
<!--配置切面 advice-ref:工厂中通知id pointcut-ref:工厂切入点唯一标识-->
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc"/>
</aop:config>
这样切面就配置成功了,进行测试就行了
EmpServiceImpl类
//原始业务对象 目标对象
public class EmpServiceImpl implements EmpService {
@Override
public void save(String name) {
System.out.println("处理业务逻辑调用save DAO~~"+name);
}
@Override
public String find(String name) {
System.out.println("处理业务调用 find DAO~~~"+name);
return name;
}
}
测试类
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/spring.xml");
EmpService empService = (EmpService) context.getBean("empService");
System.out.println(empService.getClass());
//代理对象
empService.find("小陈");
}
}

这样便得到了结果,而且他是动态代理对象
2.后置通知,需要创建一个advice通知类,并实现AfterReturningAdvice接口
//参数1:目标方法返回值 参数2: 当前执行方法对象 参数3:执行方法参数 参数4:目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("==========进入后置通知=========");
System.out.println("返回值: "+returnValue);
System.out.println("方法名: "+method.getName());
System.out.println("方法的参数: " + args[0]);
System.out.println("目标对象: " +target);
}
切面构建方法和前置通知一样,可以说前置和后置通知是非常相似的
3.异常通知,需要创建一个advice通知类,并实现ThrowsAdvice接口
异常通知时比较特殊的,当继承了ThrowsAdvice接口后,他不会要你强制实现其方法,但如果你不实现他的一个方法就会报错,其实这个接口是存在多个方法的,我们只需要找自己需要的一个方法来实现他就可以了
点进ThrowsAdvice接口的源码就可以看到这几个方法了,他们被注释了
* <pre class="code">public void afterThrowing(Exception ex)</pre>
* <pre class="code">public void afterThrowing(RemoteException)</pre>
* <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre>
* <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre>
选择一个方法来进行实现
//出现异常时执行通知处理
public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
System.out.println("===========进入异常通知============");
System.out.println("方法名: "+method.getName());
System.out.println("方法的参数: " + args[0]);
System.out.println("目标对象: " +target);
System.out.println("异常信息: "+ex.getMessage());
}
配置切面和前置通知是相同的,但出现异常时就会进入到异常通知了
4.环绕通知(重点),需要创建一个advice通知类,并实现MethodInterceptor接口,MethodInterceptor是方法拦截器的意思
//参数1: invocation 获取当前执行方法 获取当前执行方法参数 获取目标对象
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("==========进入环绕通知===========");
// System.out.println("当前执行方法:" +invocation.getMethod().getName());
// System.out.println("方法的参数: "+invocation.getArguments()[0]);
// System.out.println("获取当前的目标对象: "+invocation.getThis());
try{
long start = new Date().getTime();
//放行目标方法
Object proceed = invocation.proceed();//继续处理
long end = new Date().getTime();
System.out.println("方法: "+invocation.getMethod().getName()+",本次执行了 ["+(end-start)+"] ms!");
return proceed;
}catch (Exception e){
e.printStackTrace();
System.out.println("出现异常时业务处理");
}
return null;
}
可以看到环绕通知是非常独特的,其中最重要的一句代码是Object proceed = invocation.proceed();,这句代码是放行方法的意思,如果堆方法进行了放行且方法有返回值,那么就会得到一个Object 的返回值对象,我们需要将他返回给用户,可以看到环绕通知可以很轻松的做到对方法的性能测试的功能。
切面配置也是相同的
<!--注册通知类-->
<bean id="methodInvokeTimeAdvice" class="com.baizhi.advices.MethodInvokeTimeAdvice"></bean>
<!--配置切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pc" expression="within(com.baizhi.service.DeptServiceImpl)"/>
<!--组装切面-->
<aop:advisor advice-ref="methodInvokeTimeAdvice" pointcut-ref="pc"/>
</aop:config>
当然也可以配合注解来进行通知类的注入,那么就不需要再spring配置文件中声明通知类了
关于在spring配置文件中配置切面,其实是有多种方式的
其中配置切入点有两种方式
<!--配置切入点-->
<aop:pointcut id="pc" expression="execution(* aop.*ServiceImpl.*(..))"/>
<aop:pointcut id="pc" expression="within(com.baizhi.service.DeptServiceImpl)"/>
一种是通过execution方式,另一种是通过within方式,他们间的区别在与细粒度的不同,execution比起within具有更高的细粒度,因为他可以切到方法层面,而within只能切到类的层面,within等于是全部方法都切了,效率高。
execution表达式的意思是 返回值 aop.*ServiceImpl.方法名(方法参数) …代表不限制参数
组装切面也有两种方式
<!--组装切面-->
<aop:advisor advice-ref="methodInvokeTimeAdvice" pointcut-ref="pc"/>
<aop:aspect>
<aop:around method="invoke" pointcut-ref="pc"/>
</aop:aspect>
他们的区别在于aop:advisor 中的advice-ref写的是增强类,并且这个类必须实现AOP的增强接口,例如实现了MethodInterceptor等,这样才可以使用aop:advisor
而aop:aspect只需要最简单的bean就可以了,aop:around代表的是环绕方法,还有其他的方法,method写的是方法名,这便是他们的不同
可以看出第一种是比较好的,因为配置文件会少很多。
这就是spring中的AOP编程了