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