Spring核心之二:AOP(Aspect Oriented Programming) --- 面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
专业术语:
- Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
- Pointcut(切入点): 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.
- Advice(通知/增强): 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
- Target(目标对象): 代理的目标对象
- Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程.
spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入. - Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类Aspect(切面): 是切入点和通知(引介)的结合
动态代理:AOP的核心是java的动态代理技术,比它更简单。
看一个例子
一个类有几个简单的方法
1 package com.atguigu.spring.aop.helloword; 2 3 public class Calculator implements Calcul{ 4 5 public int add(int i,int j){ 6 return i+j; 7 }; 8 public int sub(int i,int j){ 9 return i-j; 10 }; 11 public int div(int i,int j){ 12 return i/j; 13 }; 14 public int mul(int i,int j){ 15 return i*j; 16 }; 17 }
如果要你给每个方法都加上日志,好在执行的时候知道调用了哪个方法、什么参数和什么结果,那就得这样
1 package com.atguigu.spring.aop.helloword; 2 3 public class Calculator implements Calcul{ 4 5 public int add(int i,int j){ 6 System.out.println("The method add begins with: ["+i+","+j+"]"); 7 System.out.println("The method add ends with: "+(i+j)); 8 return i+j; 9 }; 10 public int sub(int i,int j){ 11 System.out.println("The method sub begins with: ["+i+","+j+"]"); 12 System.out.println("The method sub ends with: "+(i-j)); 13 return i-j; 14 }; 15 public int div(int i,int j){ 16 System.out.println("The method div begins with: ["+i+","+j+"]"); 17 System.out.println("The method div ends with: "+i/j); 18 return i/j; 19 }; 20 public int mul(int i,int j){ 21 System.out.println("The method mul begins with: ["+i+","+j+"]"); 22 System.out.println("The method mul ends with: "+i*j); 23 return i*j; 24 }; 25 }
看每一个方法的日志,都是很规范的,很整齐的。但就是太麻烦了,太重复了。而且要改日志的格式,那就得一个一个的改,这样肯定不行。应该要把日志提取出来,让它重复使用。思想如下:
1 package com.atguigu.spring.aop.helloword; 2 3 public int Allmethod(Method method,int j,int j){ 4 System.out.println("The method "+method+" begins with: ["+i+","+j+"]"); 5 int result=method(i.j); 6 System.out.println("The method "+method+" ends with: "result); 7 8 9 }
这样,代码就整洁多了,要改日志也只要改一次。其实这个例子不是很准确,好像是一种聚合式代理。学AOP,一定要先学动态代理
推荐一个视频,讲动态代理原理:http://www.imooc.com/learn/214
这个包含了学spring需要先学的各个知识:http://stamen.iteye.com/blog/1507535
AspectJ:java社区最完整最完善的AOP框架;
AspectJ支持的5种类型的通知注解:
@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法执行之后执行
@AfterRunning:返回通知,在方法返回结果后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行
@Around:环绕通知,围绕着方法执行
Spring也有自身的AOP框架。可以使用基于AspectJ注解或基于XMl配置的AOP。
注解方式:
1、除spring基本jar包,额外需要加入jar包:aopalliance、aspectj.weaver、aop.RELRESE、aspects.RELEASE
2、配置自动扫描
1 <!-- 自动扫描,加入IOC容器 --> 2 <context:component-scan base-package="com.atguigu.spring.aop.impl"></context:component-scan> 3 4 <!-- 是Aspect注解起作用:自动为匹配的类生产代理对象 --> 5 <aop:aspect-autoproxy></aop:aspect-autoproxy>
3、创建接口和实现类
1 package com.atguigu.spring.aop.impl; 2 //接口 3 public interface Calculator { 4 5 public int add(int i,int j); 6 public int sub(int i,int j); 7 public int div(int i,int j); 8 public int mul(int i,int j); 9 }
1 package com.atguigu.spring.aop.impl; 2 //实现类 3 import org.springframework.stereotype.Component; 4 //加入注解 5 @Component 6 public class CalculatorImpl implements Calculator{ 7 8 public int add(int i,int j){ 9 return i+j; 10 }; 11 public int sub(int i,int j){ 12 return i-j; 13 }; 14 public int div(int i,int j){ 15 return i/j; 16 }; 17 public int mul(int i,int j){ 18 return i*j; 19 } 20 }
创建日志切面:
1 package com.atguigu.spring.aop.impl; 2 3 import org.springframework.stereotype.Component; 4 import org.springframework.test.context.transaction.BeforeTransaction; 5 6 //吧这个类申明为一个切面:要把该类放入到IOC容器、再声明为一个切面 7 @Aspect 8 @Component 9 public class LogginfAspect { 10 //声明该方法是一个前置通知,在目标方法开始前执行 11 //@Before("excution(修饰符 + 返回类型 + 类名,去参数名)") 12 @Before("excution(public int com.atguigu.spring.aop.impl.CalculatorImpl.add(int, int))"); 13 public void befored(){ 14 System.out.println("The method begins: "); 15 } 16 }
最后调用main方法
1 public class Main { 2 3 public static void main(String[] args) { 4 5 ApplicationContext ctx=new ClassPathXmlApplicationContext("application.xml"); 6 Calculator cal = (Calculator) ctx.getBean(Calculator.class); 7 int result=cal.add(3,6); 8 System.out.println("result: "+result); 9 } 10 11 }
结果:
若要获取类名,参数等,在切面改就行:
1 //吧这个类申明为一个切面:要把该类放入到IOC容器、再声明为一个切面 2 @Aspect 3 @Component 4 public class LogginfAspect { 5 //声明该方法是一个前置通知,在目标方法开始前执行 6 //@Before("excution(public + 全类名,去参数名)") 7 @Before("excution(public int com.atguigu.spring.aop.impl.CalculatorImpl.add(int, int))"); 8 public void beforedMethod(JoinPoint joinPoint){ 9 String methodName=joinPoint.getSignature().getName(); 10 List<Object> args = Arrays.arrays(joinPoint.getArgs()) 11 System.out.println("The method "+ methodName +"begins: "+args); 12 } 13 }
结果:
@Before("excution(public int com.atguigu.spring.aop.impl.CalculatorImpl.add(int, int))");
这句话,只是在add方法才有用,变成所有方法:把add改成*:
@Before("excution(public int com.atguigu.spring.aop.impl.CalculatorImpl.*(int, int))");
同样,还可以改成这样
@Before("excution(* int com.atguigu.spring.aop.impl.CalculatorImpl.*(int, int))");
@Before("excution(* com.atguigu.spring.aop.impl.CalculatorImpl.*(int, int))");(表示前面两个都是*)
注意:
对于后置通知
对于返回通知:
对于异常通知:
对于环绕通知,它的功能最强大,可以包含前面所以的通知,但并不意味着最常用
注意:如果有多个切面,有默认的先后执行顺序。可以用@Order(num)定义优先级,num越小,优先级越高。
注意:使用切入点,后面的切面就可以使用这个切点,写法更整洁。
如:前置通知写成:@Before("declareJointPointExpression()");
后置通知写成:@After("declareJointPointExpression()");
其他的同理。
再看一下笔记: