AOP全称是Aspect Oriented Programing,通常译为面向切面编程。利用AOP可以对面向对象编程做很好的补充。
用生活中的改装车比喻,工厂用面向对象的方法制造好汽车后,车主往往有些个性化的想法,但是又不想对车进行大规模的拆卸、替换零件,这时可以买一些可替换的零件、装饰安装到汽车上,并且这些改装应该很容易拆卸,以避免验车时无法通过。
先看一个实际例子:有一个用户登录的方法,某一段时间内我们希望能够临时监控执行时间,但是又不想直接在方法上修改,用AOP方案实现如下。
UserService类
用sleep随机时间来模拟用户登录消耗的时间
- import java.util.Random;
- public class UserService {
- public void login(String userName, String password) {
- try {
- Thread.sleep(new Random(47).nextInt(100));
- } catch (InterruptedException e) {}
- System.out.println("UserService: 用户" + userName + "登录成功");
- }
- }
PerformanceMonitorUserService类,spring将把它当作UserService的替身(代理,Proxy)
- import java.util.concurrent.TimeUnit;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- @Aspect
- public class PerformanceMonitorUserService {
- @Around("execution(* login(..))")
- public void aroundLogin(ProceedingJoinPoint pjp) {
- String userName = pjp.getArgs()[0].toString();
- long begin = System.nanoTime();
- try {
- pjp.proceed();
- } catch (Throwable e) {
- e.printStackTrace();
- }
- long end = System.nanoTime();
- System.out.println("PerformanceMonitorUserService: 用户" + userName + "登录耗时" + TimeUnit.MILLISECONDS.convert((end - begin), TimeUnit.NANOSECONDS) + "毫秒");
- }
- }
applicationContext.xml,放在src根目录
- <?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:p="http://www.springframework.org/schema/p"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
- <aop:aspectj-autoproxy />
- <bean id="userService" class="demo.aop.UserService" />
- <bean class="demo.aop.PerformanceMonitorUserService" />
- </beans>
需要添加的jar包
测试代码
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- public class Client {
- public static void main(String[] args) {
- ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- UserService userService = (UserService)ctx.getBean("userService");
- userService.login("Tom", "123456");
- }
- }
- UserService: 用户Tom登录成功
- PerformanceMonitorUserService: 用户Tom登录耗时71毫秒
这样就保持了UserService业务的纯粹性,避免非业务代码和业务代码混合在一起。如果希望取消时间监控,只需要删除applicationContext里的<bean class="demo.aop.PerformanceMonitorUserService" />即可。
Spring是如何做到的呢?底层的两大功臣是JDK的动态代理和CGLib动态代理技术。我们以JDK的动态代理技术来重现上面的过程
UserService接口(JDK动态代理只能为接口创建代理,所以先抽象了一个接口)
- public interface UserService {
- void login(String userName, String password);
- }
实现类
- import java.util.Random;
- public class UserServiceImpl implements UserService {
- public void login(String userName, String password) {
- try {
- Thread.sleep(new Random(47).nextInt(100));
- } catch (InterruptedException e) {}
- System.out.println("UserService: 用户" + userName + "登录成功");
- }
- }
代理类
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.util.concurrent.TimeUnit;
- public class PerformanceMonitorUserService implements InvocationHandler {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- long begin = System.currentTimeMillis();
- Object obj = method.invoke(target, args);
- long end = System.currentTimeMillis();
- System.out.println("PerformanceMonitorUserService: 用户" + args[0] + "登录耗时" + TimeUnit.MILLISECONDS.convert((end - begin), TimeUnit.NANOSECONDS) + "毫秒");
- return obj;
- }
- private Object target;
- public PerformanceMonitorUserService(Object target) {
- this.target = target;
- }
- }
测试代码
- import java.lang.reflect.Proxy;
- public class Client {
- public static void main(String[] args) {
- UserService target = new UserServiceImpl();
- PerformanceMonitorUserService handler = new PerformanceMonitorUserService(target);
- UserService proxy = (UserService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
- target.getClass().getInterfaces(), handler);
- proxy.login("Tom", "123456");
- }
- }
从上面的代码可以看出,AOP的原理就是创建代理,在运行时我们开发的业务逻辑类已经被替换成添加了增强代码的代理类,而Spring帮我们省略了这些繁琐和重复的步骤。