Spring AOP简介(代理模式,动态代理)

什么是AOP

AOP(Aspect-Oriented-Programming,面向切面编程)是面向对象编程的一种补充,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它的主要实现技术有spring aop和aspectJ。
解释看起来比较抽象,我们先通过传统的方式模拟实现日志记录功能

传统方式

/*计算接口*/
public interface Operation {
	double calculate(double num1,double num2);
}
/*实现计算接口的加法类,还有减乘除三个方法这里不再赘述*/
public class Add implements Operation {
	public double calculate(double num1, double num2) {
			System.out.println("正在写计算之前日志");
			double rtn=-1;
			try {
				rtn=num1+num2;//业务功能代码
				System.out.println("正在写计算之后日志");
			} catch (Exception e) {
				System.out.println("正在写出现异常时的日志");
			}
			return rtn;
	}
}
/*测试类*/
public class Main {
	public static void main(String[] args) {
		Operation operation=new Add();
		double rtn=operation.calculate(8,9 );
		System.out.println(rtn);
	}
}

可以看出,日志的写入需要分别在各实现类中重复写入日志记录的代码,如果日志需求发生变化,必须修改所有的模块,但是实际的业务功能代码只有一句,这就导致核心业务逻辑变得复杂。

普通代理模式

我们可以通过一种设计模式——代理模式,解决代码冗余。

/*计算接口*/
public interface Operation {
	double calculate(double num1,double num2);
}
/*实现计算接口的加法类*/
public class Add implements Operation {
	public double calculate(double num1, double num2) {
			return num1+num2;
	}
}
/*代理类*/
public class OperationProxy implements Operation {
	Operation operation=null;
	
	public OperationProxy(Operation operation) {
		this.operation=operation;
	}
	@Override
	public double calculate(double num1, double num2) {
		System.out.println("正在写计算之前日志");
		double num=-1;
		try {
			num=operation.calculate(num1, num2);//业务代码
			System.out.println("正在写计算之后日志");
		} catch (Exception e) {
			System.out.println("正在写出现异常时的日志");
		}
		return num;
	}
}
/*测试类*/
public class Main {
	public static void main(String[] args) {
		Operation add=new Add();
		OperationProxy proxy=new OperationProxy(add);
		double rtn=proxy.calculate(8, 9);
		System.out.println(rtn);
	}
}

但是这种方式要求代理类必须和它的代理目标有共同的接口,意思是这个代理类不能代理其它的。。。

动态代理

/*计算接口*/
public interface Operation {
	double calculate(double num1,double num2);
}
/*实现计算接口的加法类*/
public class Add implements Operation {
	public double calculate(double num1, double num2) {
			return num1+num2;
	}
}
/**
 * 动态代理
 * 需要继承InvocationHandler接口
 * @author yang
 *
 */
public class DynamicProxy implements InvocationHandler {
	
	private Object target;//被代理的对象,Operation
	public DynamicProxy(Object target) {
		this.target=target;
	}
	/*
	 * 反射调用该接口
	 * proxy:代理对象
	 * method:需要执行的方法
	 * args:需要执行的方法参数
	 * 返回方法的执行结果
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("正在写计算之前日志......");
		Object rtn=-1;
		try {
			rtn=method.invoke(target, args);
			System.out.println("正在写计算之后日志......");
			
		} catch (Exception e) {
			System.out.println("正在写出现异常时的日志......");
		}
		return rtn;
	}
	
	/*获取代理对象*/
	public static Object createProxy(Object target) {
		return Proxy.newProxyInstance(target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),new DynamicProxy(target));
	}
}

/*测试类*/
public class Main {
	public static void main(String[] args) {
		Operation add=new Add();
		Operation op=(Operation) DynamicProxy.createProxy(add);
		double num=op.calculate(8, 9);
		System.out.println(num); 
	}
}

将完成业务功能的代码与辅助功能(记录日志)分开,业务逻辑更加清晰

AOP方式(JDK动态代理)

依赖AspectJ类库(pom.xml中)

spring-aspect(自动依赖aspectjweaver)和aopalliance

spring配置文件引用命名空间aop

Spring AOP简介(代理模式,动态代理)_第1张图片

并在配置文件中启用aspectJ注解支持


<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
	<context:component-scan base-package="day0915.cal">context:component-scan>
beans>

在实现类中加入注解 ,将其的bean放入ioc容器中

@Component
public class Add implements Operation {
	public double calculate(double num1, double num2) {
			return num1+num2;
	}
}

编写切面类

/**
 * 切面类
 * Aspect注解表示该类为切面类
 * @author yang
 *
 */
@Component
@Aspect
public class OperationAspect {
	//前置通知
	@Before("execution(* *.calculate(..))")
	public void before(){
		System.out.println("业务执行之前执行的方法")
		
	}

}

/*测试类*/
public class Main {
	public static void main(String[] args) {
		ApplicationContext ac=new ClassPathXmlApplicationContext("applictioncontext4.xml");
		/*
		 * 这里使用了切面类,创建容器时new Add()还需要经过AOP
		 * 进入ioc容器的是实例类的动态代理类,而不是实例类
		 * 所以最好还是用id来获取
		 */
//		Operation operation=ac.getBean(Add.class);
		Operation operation=(Operation) ac.getBean("add");//动态获取代理
		double num=operation.calculate(8, 9);
		System.out.println(num);

	}
}

可见每个事务逻辑位于一个位置,代码不分散,便于维护和升级,并且对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

spring AOP术语

(1)通知(Advice):切面必须要实现的功能(比如上面例子的日志记录功能),切面类中加入了通知注解的方法。

AspectJ支持的五种通知注解:

1. @Before 前置通知,在业务代码执行前执行
2. @After 后置通知,在业务代码执行之后执行,不管业务代码是否正常执行(类似异常捕获时的finally,不管try中的方法是否异常都会执行)
3. @AfterRetruning 返回通知,业务代码执行成功之后才能执行
4. @AfterThrowing 异常通知,业务逻辑抛出异常之后执行
5. @Around 环绕通知,围绕着方法执行,包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

(2)连接点(Joinpoint):spring允许通知的地方,方法前后、或者抛出异常时,spring只支持方法连接点

(3)切点(pointcut):一个类中有很多方法,每个方法都有几个连接点,但是你并不希望有的方法都被通知,此时需要使用切入点表达式来匹配你希望被通知的方法,aop就是通过切点来定位到想要的连接点

(4)切面(Aspect):通知和切入点的结合,是加上注解@aspect的类,简单来说,通知就是说明要做什么,什么时候做,切点就是在哪里做,这就是一个切面的完整定义

(5)目标(target):要被通知的对象,也就是真正的业务逻辑。如果想只用这个切面通知的类,使用JoinPoint.getTarget()方法获取

(6)代理(proxy):向目标对象应用通知之后创建的对象

AOP原理

spring用代理类将切面包裹住,然后将它们织入到ioc容器的bean中,通过动态代理的方式将代理类伪装成目标类(就是上面例子中的add类),代理类会截取对目标类中方法的调用,然后先执行切面,然后再把调用转发给目标类的bean。
要实现这个伪装还要躲避JVM的检查,有两种方式:

  1. spring使用JDK的java.lang…reflect.Proxy类,动态生成一个新的类来实现和目标类相同的接口,织入通知,并且把这些接口的任何调用都转发给目标类,但是这方法有个局限,如果目标类没有实现某个接口,就不能用这方法了
  2. spring使用CGLIB库生成目标类的一个子类,织入通知,这种方法是在调用这个子类的方法,一些无法继承的方法就不能用了。
    相比之下,还是第一种方法好些,他能更好的实现低耦合。

你可能感兴趣的:(aop,spring)