spring aop原理和实现

一、aop是什么

1.AOP面向方面编程基于IoC,是对OOP的有益补充;

2.AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了 多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的 逻辑或责任封装起来,比如日志记录,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

3.AOP代表的是一个横向的关 系,将“对象”比作一个空心的圆柱体,其中封装的是对象的属性和行为;则面向方面编程的方法,就是将这个圆柱体以切面形式剖开,选择性的提供业务逻辑。而 剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹,但完成了效果。

4.实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

二、aop的原理

1.采用动态代理,对被代理对象和特定处理进行修饰和封装,得到代理对象,从使得被代理对象中不需切入任何的代码

2.采用静态织入,如AspectJ,使用其特定的语法创建切面,在编译期间将切面织入代码中。

三、aop的基本概念

切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。

通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

目标(Target):被通知对象。

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

切入点(PointCut):切面通知执行的“地点”的定义。

连接点(JointPoint):与切入点匹配的执行点。

四、aspectJ的织入方式及其原理概要

ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

五、spring aop的实现

ApplicationContext.xml 加入

<aop:aspectj-autoproxy/>

创建切面处理类

@Aspect
@Component 
public class AspectHandler { 
    
  @Pointcut("execution(* com.marving.service.BaseServ+.*(..))") 
  private void doMethod() { 
  } 
  
    /** 
   * This is the method which I would like to execute before a selected method 
   * execution. 
   */
  @Before("doMethod()") 
  public void beforeAdvice() { 
    System.out.println("before method invoked."); 
  } 
  
  /** 
   * This is the method which I would like to execute after a selected method 
   * execution. 
   */
  @After("doMethod()") 
  public void afterAdvice() { 
    System.out.println("after method invoked."); 
  } 
  
  // 配置controller环绕通知,使用在方法aspect()上注册的切入点 
  @Around("doMethod()") 
  public Object around(ProceedingJoinPoint pjp) throws Throwable{ 
    Object result = null; 
    String methodName = pjp.getSignature().getName(); 
    try { 
      System.out.println("The method [" + methodName + "] begins with " + Arrays.asList(pjp.getArgs())); 
      result = pjp.proceed(); 
    } catch (Throwable e) { 
      System.out.println("The method [" + methodName + "] occurs expection : " + e); 
      throw new RuntimeException(e); 
    } 
    System.out.println("The method [" + methodName + "] ends"); 
    return result; 
  } 
} 

使用@Pointcut注解进行定义,应用到通知函数afterDemo()时直接传递切点表达式的函数名称myPointcut()即可,比较简单,下面接着介绍切点指示符。

1.切入点指示符

类型签名表达式

within(<type name>)
//匹配com.mobanker.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.mobanker.dao..*)")
// 匹配UserDaoImpl类中所有方法
@Pointcut("within(com.mobanker.dao.UserDaoImpl)")
// 匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.mobanker.dao.UserDaoImpl+)")
// 匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.mobanker.dao.UserDao+)")

方法签名表达式

//scope :方法作用域,如public,private,protect
// returnt-type:方法返回值类型
// fully-qualified-class-name:方法所在类的完全限定名称
// parameters 方法参数
execution(<scope><return-type><fully-qualified-class-name>.*(parameters))
//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.mobanker.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.mobanker.dao.UserDaoImpl.*(..))")
// 匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.mobanker.dao.UserDaoImpl.*(..))")
// 匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.mobanker.dao.UserDaoImpl.*(int , ..))")

其它指示符

bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;

//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}

this :用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配

//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.mobanker.spring.springAop.dao.UserDao)")
private void myPointcut2(){}

target :用于匹配当前目标对象类型的执行方法;

//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.mobanker.spring.springAop.dao.UserDao)")
private void myPointcut3(){}

@within:用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;

//匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("@within(com.mobanker.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}

@annotation(com.mobanker.spring.MarkerMethodAnnotation) : 根据所应用的注解进行方法过滤

//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.mobanker.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}

2.5种通知函数

前置通知@Before

  前置通知通过@Before注解进行标注,并可直接传入切点表达式的值,该通知在目标函数执行前执行,注意JoinPoint,是Spring提供的静态变量,通过joinPoint 参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等,,该参数是可选的。

/**
 * 前置通知
 * @param joinPoint 该参数可以获取目标对象的信息,如类名称,方法参数,方法名称等
 */
@Before("execution(* com.mobanker.spring.springAop.dao.UserDao.addUser(..))")
public void before(JoinPoint joinPoint){
    System.out.println("我是前置通知");
}

后置通知@AfterReturning
  通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完成不用声明出来。如下

/**
* 后置通知
* returnVal,切点方法执行后的返回值
*/
@AfterReturning(value="execution(* com.mobanker.spring.springAop.dao.UserDao.*User(..))",returning = "returnVal")
public void AfterReturning(JoinPoint joinPoint,Object returnVal){
   System.out.println("我是后置通知...returnVal+"+returnVal);
}

异常通知 @AfterThrowing

  该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数,需要时加上即可,如下:

/**
* 抛出通知
* @param e 抛出异常的信息
*/
@AfterThrowing(value="execution(* com.mobanker.spring.springAop.dao.UserDao.addUser(..))",throwing = "e")
public void afterThrowable(Throwable e){
  System.out.println("出现异常:msg="+e.getMessage());
}

最终通知 @After

  该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行。

/**
 * 无论什么情况下都会执行的方法
 * joinPoint 参数
 */
@After("execution(* com.mobanker.spring.springAop.dao.UserDao.*User(..))")
public void after(JoinPoint joinPoint) {
    System.out.println("最终通知....");
}

环绕通知@Around

  环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否指向执行,但即使如此,我们应该尽量以最简单的方式满足需求,在仅需在目标方法前执行时,应该采用前置通知而非环绕通知。案例代码如下第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,proceed()的返回值就是环绕通知的返回值。同样的,ProceedingJoinPoint对象也是可以获取目标对象的信息,如类名称,方法参数,方法名称等等。

@Around("execution(* com.mobanker.spring.springAop.dao.UserDao.*User(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("我是环绕通知前....");
    //执行目标函数
    Object obj= (Object) joinPoint.proceed();
    System.out.println("我是环绕通知后....");
    return obj;
}

 图片和内容:https://blog.csdn.net/javazejian/article/details/56267036

 

 

posted @ 2018-12-12 17:32  饕餮灬灬  阅读(319)  评论(0编辑  收藏  举报