1.AOP概念:Aspect Oriented Programming 面向切面编程spring
2.做用:本质上来讲是一种简化代码的方式数据库
继承机制express
封装方法编程
动态代理ide
……测试
3.情景举例ui
①数学计算器接口[MathCalculator]spa
int add(int i,int j);.net
int sub(int i,int j);代理
int mul(int i, int j);
int div(int i,int j);
②提供简单实现[EasyImpl]
③在简单实现的基础上让每个计算方法都可以打印日志[LoginImpl]
④缺陷
[1]手动添加日志繁琐,重复
[2]统一修改不便
[3]对目标方法原本要实现的核心功能有干扰,使程序代码很臃肿,不易于开发维护
⑤使用动态代理实现
[1]建立一个类,让这个类可以提供一个目标对象的代理对象
[2]在代理对象中打印日志
4.AOP术语!
AOP概述
●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。
●Spring的AOP既可使用xml配置的方式实现,也可使用注解的方式来实现!
4.1 横切关注点
从每一个方法中抽取出来的同一类非核心业务。(抽离到方法中处理非核心业务)
4.2 切面(Aspect)
封装横切关注点信息的类,每一个关注点体现为一个通知方法。
4.3 通知(Advice)
切面必需要完成的各个具体工做
4.4 目标(Target)
被通知的对象
4.5 代理(Proxy)
向目标对象应用通知以后建立的代理对象
4.6 链接点(Joinpoint)
横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。
在应用程序中可使用横纵两个坐标来定位一个具体的链接点:
4.7 切入点(pointcut):
定位链接点的方式。每一个类的方法中都包含多个链接点,因此链接点是类中客观存在的事物。
若是把链接点看做数据库中的记录,那么切入点就是查询条件——AOP能够经过切入点定位到特定的链接点。
切点经过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法做为链接点的查询条件。
5.在Spring中使用AOP实现日志功能
①Spring中可使用注解或XML文件配置的方式实现AOP。
②导入jar包
com.springsource.net.sf.cglib -2.2.0.jar
com.springsource.org.aopalliance-1.0.0 .jar
com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar
commons-logging-1.1.3. jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE. jar
③开启基于注解的AOP功能
< aop:aspectj-autoproxy />
④声明一个切面类,并把这个切面类加入到IOC容器中
@Aspect//表示这是一个切面类
@Component//加入IOC容器
public class LogAspect {}
⑤在切面类中声明通知方法
[1]前置通知:@Before
[2]返回通知:@AfterReturning
[3]异常通知:@AfterThrowing
[4]后置通知:@After
[5]环绕通知:@Around :环绕通知是前面四个通知的集合体!
1 @Aspect//表示这是一个切面类
2 @Component//将本类对象加入到IOC容器中!
3 public class LogAspect { 4 @Before(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))") 5 public void showBeginLog(){ 6 System.out.println("AOP日志开始"); 7 } 8 @After(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))") 9 public void showReturnLog(){ 10 System.out.println("AOP方法返回"); 11 } 12 @AfterThrowing(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))") 13 public void showExceptionLog(){ 14 System.out.println("AOP方法异常"); 15 } 16 @AfterReturning(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))") 17 public void showAfterLog(){ 18 System.out.println("AOP方法结束"); 19 } 20 }
⑥被代理的对象也须要加入IOC容器
@Component//加入IOC容器
1 public class MathCalculatorImpl { 2 public int add(int i,int j){ 3 int result = i+j; 4 return result; 5 } 6 public int sub(int i,int j){ 7 int result = i-j; 8 return result; 9 } 10 public int multi(int i,int j){ 11 int result = i*j; 12 return result; 13 } 14 public int divide(int i,int j){ 15 int result = i/j; 16 return result; 17 } 18 }
1.上述案例经过junit测试,会发现,咱们调用目标类的四个方法只有add方法被加入了4个通知,
若是想全部的方法都加上这些通知,能够在切入点表达式处,将execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 换成:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.*(int, int))这样只要是有两个参数,且参数类型为int的方法在执行的时候都会执行其相应的通知方法!
2.①切入点表达式的语法格式
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
2.1 做用
经过表达式的方式定位一个或多个具体的链接点。
2.2 语法细节
①切入点表达式的语法格式
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表])) |
②举例说明
表达式 |
execution(* com.atguigu.spring.ArithmeticCalculator.*(..)) |
含义 |
ArithmeticCalculator接口中声明的全部方法。 第一个“*”表明任意修饰符及任意返回值。 第二个“*”表明任意方法。 “..”匹配任意数量、任意类型的参数。 若目标类、接口与该切面类在同一个包中能够省略包名。 |
表达式 |
execution(public * ArithmeticCalculator.*(..)) |
含义 |
ArithmeticCalculator接口的全部公有方法 |
表达式 |
execution(public double ArithmeticCalculator.*(..)) |
含义 |
ArithmeticCalculator接口中返回double类型数值的方法 |
表达式 |
execution(public double ArithmeticCalculator.*(double, ..)) |
含义 |
第一个参数为double类型的方法。 “..” 匹配任意数量、任意类型的参数。 |
表达式 |
execution(public double ArithmeticCalculator.*(double, double)) |
含义 |
参数类型为double,double类型的方法 |
③在AspectJ中,切入点表达式能够经过 “&&”、“||”、“!”等操做符结合起来。
表达式 |
execution (* *.add(int,..)) || execution(* *.sub(int,..)) |
含义 |
任意类中第一个参数为int类型的add方法或sub方法 |
execution(* *.*(..))
1.任意参数,任意类型
2.任意返回值
3.用@PointCut注解统一声明,而后在其它通知中引用该统一声明便可!
须要注意的是:权限是不支持写通配符的,固然你能够写一个*表示全部权限全部返回值!
4.最详细的切入点表达式:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
5.最模糊的切入点表达式:
execution (* *.*(..))
6..统一声明切入点表达式
@Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")
public void myPointCut(){}
三、通知方法的细节
①在通知中获取目标方法的方法名和参数列表
[1]在通知方法中声明一个JoinPoint类型的形参
[2]调用JoinPoint对象的getSignature()方法获取目标方法的签名
[3]调用JoinPoint对象的getArgs()方法获取目标方法的实际参数列表
②在返回通知中获取方法的返回值
[1]在@AfterReturning注解中添加returning属性
@AfterReturning (value="myPointCut()", returning= "result")
[2]在返回通知的通知方法中声明一个形参,形参名和returning属性的值一致
showReturnLog(JoinPoint joinPoint, Object result)
③在异常通知中获取异常对象
[1]在@ AfterThrowing注解中添加throwing属性
@AfterThrowing (value="myPointCut()",throwing= "throwable" )
[2]在异常通知的通知方法中声明一个形参,形参名和throwing属性值一致
showExceptinLog(JoinPoint joinPoint, Throwable throwable)
根据接口类型获取target对象时,实际上真正放在IOC容器中的对象是代理对象,而并非目标对象自己!
四、.环绕通知:@Around
4.1.环绕通知须要在方法的参数中指定JoinPoint的子接口类型ProceedingJoinPoint为参数
@Around(value="pointCut()")
public void around(ProceedingJoinPoint joinPoint){
}
4.2.环绕通知会将其余4个通知能干的,本身都给干了!
注意:@Around修饰的方法必定要将方法的返回值返回!自己至关于代理!
1 @Around(value="pointCut()") 2 public Object around(ProceedingJoinPoint joinPoint){ 3 Object[] args = joinPoint.getArgs(); 4 Signature signature = joinPoint.getSignature(); 5 String methodName = signature.getName(); 6 List<Object> list = Arrays.asList(args); 7 Object result = null; 8 try { 9 //目标方法以前要执行的操做
10 System.out.println("[环绕日志]"+methodName+"开始了,参数为:"+list); 11 //调用目标方法
12 result = joinPoint.proceed(args); 13 //目标方法正常执行以后的操做
14 System.out.println("[环绕日志]"+methodName+"返回了,返回值为:"+result); 15 } catch (Throwable e) { 16
17 //目标方法抛出异常信息以后的操做
18 System.out.println("[环绕日志]"+methodName+"出异常了,异常对象为:"+e); 19 throw new RuntimeException(e.getMessage()); 20 }finally{ 21 //方法最终结束时执行的操做!
22 System.out.println("[环绕日志]"+methodName+"结束了!"); 23 } 24 return result; 25 }
对于同一个代理对象,可j以同时有多个切面共同对它进行代理。
能够在切面类上经过@Order (value=50)注解来进行设置,值越小优先级越高!
1 @Aspect 2 @Component 3 @Order(value=40) 4 public class TxAspect { 5
6 @Around(value="execution(public * com.neuedu.aop.target.MathCalculatorImpl.*(..))") 7 public Object around(ProceedingJoinPoint joinPoint){ 8 Object[] args = joinPoint.getArgs(); 9 Signature signature = joinPoint.getSignature(); 10 String methodName = signature.getName(); 11 List<Object> list = Arrays.asList(args); 12 Object result = null; 13 try { 14 //目标方法以前要执行的操做
15 System.out.println("[事务日志]"+methodName+"开始了,参数为:"+list); 16 //调用目标方法
17 result = joinPoint.proceed(args); 18
19 //目标方法正常执行以后的操做
20 System.out.println("[事务日志]"+methodName+"返回了,返回值为:"+result); 21 } catch (Throwable e) { 22 //目标方法抛出异常信息以后的操做
23 System.out.println("[事务日志]"+methodName+"出异常了,异常对象为:"+e); 24 throw new RuntimeException(e.getMessage()); 25 }finally{ 26 //方法最终结束时执行的操做!
27 System.out.println("[事务日志]"+methodName+"结束了!"); 28 } 29 return result; 30 } 31
32 }
一、注意:上面的AOP都是经过注解实现的,AOP实际上也能够经过xml配置的方式实现!
①<!-- 1.将须要加载到IOC容器中的bean配置好 -->
<bean id="logAspect" class="com.neuedu.aop.proxy.LogAspect"></bean>
<bean id="txAspect" class="com.neuedu.aop.target.TxAspect"></bean>
<bean id="calculator" class="com.neuedu.aop.target.MathCalculatorImpl"></bean>
②<!-- 2.配置AOP,须要导入AOP名称空间 -->
<aop:config>
③<!-- 声明切入点表达式 -->
<aop:pointcut expression="execution(* com.neuedu.aop.target.MathCalculatorImpl.*(..))" id="myPointCut"/>
④<!-- 配置日志切面类,引用前面的类 ,经过order属性控制优先级-->
<aop:aspect ref="logAspect" order="25">
⑤<!-- 经过method属性指定切面类的切面方法,经过pointcut-ref指定切入点表达式 -->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<aop:after method="showAfterLog" pointcut-ref="myPointCut"/>
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/>
<aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/>
<aop:around method="around" pointcut-ref="myPointCut"/>
</aop:aspect>
⑥<!-- 配置事务切面类,引用前面的类 -->
<aop:aspect ref="txAspect" order="20">
<aop:around method="around" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
须要知道的是:事务的管理是和AOP是有很大关系的,即声明式事务的底层是用事务实现的!