AOP是Aspect Oriented Programming的简称,意思是面向切面编程。Spring AOP的实现是基于Java的代理机制,从JDK1.3开始就支持代理功能,但是性能成为一个很大问题,为了解决JDK代理性能问题,出现了CGLIB代理机制。它可以生成字节码,所以它的性能会高于JDK代理。Spring支持这两种代理方式。但是,随着JVM(Java虚拟机)的性能的不断提高,这两种代理性能的差距会越来越小。
【实例】给一个系统的业务逻辑方法添加业务日志功能。要求在其业务方法调用前记录日志,记录方法调用的时间,调用的业务方法名和调用的参数,运行效果如下:
仅模拟业务逻辑层的两个方法:register(用户注册)、comment(用户评论)。在使用Spring的时候,业务逻辑层也被称作“服务层”。
创建UserService.java用户信息业务逻辑接口。
package com.pjb.aop;
/**
* 用户信息业务逻辑接口
* @author pan_junbiao
**/
public interface UserService
{
/**
* 用户注册
*/
public boolean register(String userName, String blogUrl, String sex);
/**
* 用户评论
*/
public void comment(String userName,String comments);
}
创建UserServiceImpl.java用户信息业务逻辑实现类。
package com.pjb.aop;
/**
* 用户信息业务逻辑实现类
* @author pan_junbiao
**/
public class UserServiceImpl implements UserService
{
/**
* 用户注册
*/
@Override
public boolean register(String userName, String blogUrl, String sex)
{
System.out.println("业务方法register开始执行:");
System.out.println("用户名称:"+userName);
System.out.println("博客地址:"+blogUrl);
System.out.println("用户性别:"+sex);
System.out.println("业务方法register执行完成");
return true;
}
/**
* 用户评论
*/
@Override
public void comment(String userName, String comments)
{
System.out.println("业务方法comment开始执行:");
System.out.println("用户名称:"+userName);
System.out.println("评论内容:"+comments);
System.out.println("业务方法comment执行完成");
}
}
实现特定功能的切面代码在AOP概念中又称为“通知(Advice)”。通知分为前置通知、后置通知、环绕通知和异常通知。
这个分类是根据通知织入到业务代码时执行的时间划分的。前置通知是在方法执行前自动执行的通知;后置通知是在方法执行后自动执行的通知;环绕通知能力最强,它可以在方法调用前执行通知代码,可以决定是否还调用目标方法;异常通知是方法抛出异常时自动执行的切面代码。
这里使用前置通知。
package com.pjb.aop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
* 日志通知
* @author pan_junbiao
**/
public class LogAdvice implements MethodBeforeAdvice
{
@Override
public void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable
{
DateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
System.out.println("\n[系统日志]");
System.out.println("执行时间:" + sdf.format(new Date()));
System.out.println("方法名称:" + var1.getName());
System.out.println("执行参数:" + Arrays.toString(var2));
System.out.println("====================================================================");
}
}
编写前置通知需要实现MethodBeforeAdvice接口,这个接口要求实现的方法是:
public void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable;
参数Method var1是被通知目标方法对象;参数Object[] var2是传入被调用方法的参数;参数Object var3是被调用方法所属的对象实例。通过这些参数,我们几乎可以在方法代码中完成很多工作。
如果直接访问原来的Bean,通知代码肯定不会被执行。Spring采用“代理”的方法将通知织入到原Bean中。Spring将原Bean和通知都封装到org.springframework.aop.framework.ProxyFactoryBean类别中。用户通过访问代理类访问原Bean,这样就能保证在目标方法调用前先执行前置通知的代码了。
无需编写一行程序代码,只需要通过配置完成织入的过程即可,配置工作仍然是在Spring配置文件中完成的。
在项目中添加Spring的jar包和commons-logging.jar文件。
在src目录下创建applicationContext.xml配置文件。
com.pjb.aop.UserService
logAdvice
首先定义了原Bean“userServiceTarget”和“logAdvice”。然后定义代理类,名称为userService,我们将通过这个Bean访问业务方法。代理类有3个必须设置的属性:proxyInterfaces,表示被代理的接口;interceptorNames表示织入的通知列表;target表示被代理的原Bean。
创建AopTest.java类,编写测试代码。
package com.pjb.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 运行测试
* @author pan_junbiao
**/
public class AopTest
{
public static void main(String[] args)
{
//装载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取UserService的代理类
UserService userService = (UserService)context.getBean("userService");
//调用注册方法
userService.register("pan_junbiao的博客","https://blog.csdn.net/pan_junbiao","男");
//调用用户评论方法
userService.comment("pan_junbiao的博客","您好,欢迎访问 pan_junbiao的博客!");
}
}