AOP的正确打开方式
2017-04-21 21:48
232 查看
Aspect Oriented Programming(AOP)译作“面向切面编程”。
看名字确实非常抽象,不容易理解。我们从 是什么,为什么,如何做 来攻克它!
下面是AOP的知识网络图:
原图下载:
http://download.csdn.net/detail/qq_34149805/9821716
一、AOP到底是什么?
AOP 是一种编程思想,是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Aop采用的是横向抽取机制,取代了传统的纵向继承体系重复代码。
应用场景:事务管理,权限校验。
如果你看了上面的说法可能还是有点蒙,请看第二点的解释。
二、为什么要使用AOP?
用一个老掉牙的例子来解释:如果在调用所有方法前都要进行权限校验,在调用所有方法后都要进行日志记录,而我们不能直接在原方法修改。
public class UserviceImp implements UserService { @Override public void add() { System.out.println("add"); } @Override public void delete() { System.out.println("delete"); } }
我们第一想到的可能是装饰者模式,即直接在原类的基础上再继承出一个装饰者类,这样既不改变原来的代码又可以实现需求。
public class userDecorator implements UserService{ @Override public void add() { System.out.println("权限校验"); System.out.println("add"); System.out.println("日志记录"); } @Override public void delete() { System.out.println("权限校验"); System.out.println("delete"); System.out.println("日志记录"); } }
这样做确实可以解决问题,但却使我们的程序陷入的高度耦合,试想如果有10个service,就要抽象出10个装饰者。
AOP就可以解决这个问题,所谓切面,就是要横向切入每一个目标方法中,而不是纵向继承,拉长代码。
所以,AOP就是如何将目标方法”增强”的一个过程,或者说我们如何将我们自己定义的通知和目标方法结合起来的过程。
JAVA中的动态代理机制,可以创建目标类的代理类,从何实现在代理类中的目标方法前后执行代码。我们会在下面的代码中用JAVA的代理机制自己写一个”AOP”。
当然,代理机制只适用于实现了接口的目标类,Spring中使用了CGLIB来实现对目标类(不实现接口)的增强。
三、如何实现AOP
3.1 AOP 专有名词
下面的实现中,经常会用到一些专有名词,提前科普一下会帮助我们更好的书写程序。1.target 目标类:需要被代理的类。例如UserService
2.Joinpoint 连接点:可能被拦截到的方法。例如:所有方法
3.PointCut 切入点:已经被增强的连接点,例如:addUser()
4.advice 通知/增强,before,after
5.weaving 织入 把增强advice应用到目标对象target来创建新代理对象Proxy的过程。
6.Proxy 代理类
7.Aspect 切面:是切入点pointcut和通知advice的结合。
3.2 AOP的实现步骤
仔细看来,每种AOP的实现方式都离不开以下三步。1.创建/声明目标类。
2.创建/声明切面类(声明advice)。
3.将advice织入到目标类中。
我们接下来的程序将以以上三步展开。
3.3 手动实现AOP
3.3.1 使用JDK动态代理
1.创建目标类。public interface UserService { void add(); void update(); void delete(); }
2.创建切面类(声明advice)。
public class MyAspect{ public void before(){ System.out.println("鸡首"); } public void after(){ System.out.println("牛后"); } }
3.将advice织入到目标类中。
public static UserService cteateService(){ //1.目标类 UserService userService = new UserviceImp(); //2.切面类 MyAspect myAspect = new MyAspect(); //3.代理类:将目标类(切入点)和切面类(通知)结合-->切面 //参数2: 还可以使用 new Class[]{UserService.class} UserService proxService = (UserService) Proxy.newProxyInstance( userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { myAspect.before(); Object invoke = (UserService) method.invoke(userService, args); myAspect.after(); return invoke; } }); return proxService; }
3.3.2 CGLIB字节码增强
public static UserviceImp cteateService(){ final UserviceImp uService = new UserviceImp(); final MyAspect myAspect = new MyAspect(); /* * 代理类,采用cglib,底层创建目标类的自雷 * */ //核心类 Enhancer enhancer = new Enhancer(); //确定父类 enhancer.setSuperclass(uService.getClass()); //回调函数,等效jdk中InvocationHandler enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { myAspect.before(); Object invoke = method.invoke(uService, objects); //执行代理类的父类,执行目标类(目标类和代理类是父子关系) methodProxy.invokeSuper(o,objects); myAspect.after(); return invoke; } }); UserviceImp proxService = (UserviceImp) enhancer.create(); return proxService; }
3.4 AspectJ 实现AOP
AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP语法。3.4.1 AspectJ入门案例
1.声明目标类2.声明切面
/** * 切面类中确定通知,需要实现不同接口,接口就是规范,从而确定方法名称。 * 采用环绕通知,(必须手动执行目标方法) */ public class MyAspect implements MethodInterceptor{ @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("前方法"); Object proceed = methodInvocation.proceed(); System.out.println("后方法"); return proceed; } }
3.将advice织入到目标类中。
<?xml version="1.0" encoding="UTF-8"?> <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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 创建目标类--> <bean id="userService" class="com.aop.lishiqi.c_spring_aop.UserviceImp"></bean> <!-- 创建切面类--> <bean id="myAspect" class="com.aop.lishiqi.c_spring_aop.MyAspect"></bean> <!-- aop,添加约束 使用<aop:config>进行配置 <aop:pointcut>切入点,从目标对象获得具体方法 <aop:advisor> 特殊的切面,只有一个通知和一个切入点 advice-ref 通知引用 pointcut-ref:通知引用 切入点表达式:execution(* com.aop.lishiqi.c_spring_aop.*.*(..)) 选择方法 返回值任意 包 类名任意,方法任意 参数任意 <aop:pointcut> --> <aop:config> <aop:pointcut id="pointCut" expression="execution(* com.aop.lishiqi.c_spring_aop.*.*(..))"></aop:pointcut> <aop:advisor advice-ref="myAspect" pointcut-ref="pointCut"></aop:advisor> </aop:config> </beans>
在Spring配置文件中将目标类和切面类配置为bean。
可以发现,在
<aop:config>中使用
<aop:pointcut首先声明切入点,即我们要将那些方法织入通知。
然后使用
<aop:advisor将切入点和切面关联起来,这是其中一种写法,下面的代码我们也用
<aop:aspect来实现。
3.4.2 expression语法规则
<aop:pointcut id="pointCut" expression="execution(* com.aop.lishiqi.c_spring_aop.*.*(..))"></aop:pointcut>
这句话用来声明哪些目标方法是切入点。
expression的语法规则是:
execution(修饰符 返回值 包.类.方法(参数)throws异常)
3.4.3 通知类型
我们在入门案例中使用的是环绕通知,AspectJ中还有其他几种类型,他们各有不同的特点。前置通知:应用:各种数据校验,在方法前执行,如果排除异常阻止方法运行。
后置通知:方法正常返回后执行,如果方法中抛出异常,阻止方法运行
环绕通知:方法执行前后执行,可以阻止方法执行,必须手动执行目标方法
异常通知:抛出异常时执行
最终通知:无论发生什么最终都执行
我们可以通过实现接口和
<aop:advisor来使用他们,也可以使用自定义方法和
<aop:aspect来实现。
下面以第二种写法分别展示他们的用法。
后置通知:
public void myBefore(JoinPoint joinPoint){ System.out.println("前置通知"+joinPoint.getSignature().getName()); }
<aop:config> <aop:pointcut id="pointCut" expression="execution(* com.aop.lishiqi.c_spring_aop.*.*(..))"></aop:pointcut> <aop:aspect ref="myAspect"> <aop:before method="myBefore" pointcut-ref="myPointCut"></aop:before> </aop:aspect> </aop:config>
后置通知:
<!--后置通知 returning:通知方法第二个参数的名称 --> <aop:after-returning method="myAfterreturning" pointcut-ref="myPointCut" returning="ret"></aop:after-returning>
//第二个参数是切入点的返回值 public void myAfterreturning(JoinPoint joinPoint,Object ret){ System.out.println("后置通知"+joinPoint.getSignature().getName()+ret); }
环绕通知:
<!--环绕通知 返回值类型必须为Object 参数 ProceedingJoinPoint 需要抛出异常 执行目标方法:Object proceed = joinPoint.proceed(); 方法名 任意--> <aop:around method="myAround" pointcut-ref="myPointCut"></aop:around>
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { //手动执行目标方法 System.out.println("前通知"); Object proceed = joinPoint.proceed(); System.out.println("后通知"); return proceed; }
异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("抛出异常通知"); System.out.println(e); }
<!-- 抛出异常通知 参数1 连接点的描述对象 参数2 获得一场因戏 类型Throwable 参数名由throwing="e"配置 --> <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"></aop:after-throwing>
最终通知:
public void myAfter(JoinPoint joinPoint){ System.out.println("最终通知"); }
<!-- 最终通知 无论发生什么最终都执行 --> <aop:after method="myAfter" pointcut-ref="myPointCut"></aop:after
3.5 AOP注解开发
注解是代替XML的配置,所以以上所有的XML都可以用注解替代。扫描包:
<context:component-scan base-package="com.aop.lishiqi.d_aspect_注解"></context:component-scan>
确定AOP注解生效:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切面类:
//声明切面
@Component("myAspect")
@Aspect
public class MyAspect {
//声明切入点
@Pointcut("execution(* com.aop.lishiqi.c_spring_aop.*.*(..)) ")
private void myPointCut(){
}
@Before("execution(* com.aop.lishiqi.c_spring_aop.*.*(..))")
public void myBefore(JoinPoint joinPoint){ System.out.println("前置通知"+joinPoint.getSignature().getName()); }
@AfterReturning(value="myPointCut()",returning = "ret")
public void myAfterreturning(JoinPoint joinPoint,Object ret){
System.out.println("后置通知"+joinPoint.getSignature().getName()+ret);
}
@Around(value = "myPointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { //手动执行目标方法 System.out.println("前通知"); Object proceed = joinPoint.proceed(); System.out.println("后通知"); return proceed; }
@AfterThrowing(value = "myPointCut()",throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("抛出异常通知"); System.out.println(e); }
@After("myPointCut()")
public void myAfter(JoinPoint joinPoint){ System.out.println("最终通知"); }
}
相关文章推荐
- camera正确打开/关闭方式 和 杀死进程
- Pull Request的正确打开方式(如何在GitHub上贡献开源项目)
- Xcode 的正确打开方式——Debugging
- Xcode 的正确打开方式——Debugging
- Xcode 的正确打开方式——Debugging(转载)
- 您的网页浏览器已阻止此站点以不安全的方式打开使用Active控件。因此,此页可能显示不正确
- memset函数的正确打开方式
- Xcode 的正确打开方式——Debugging(转)
- Xcode 的正确打开方式——Debugging
- Xcode 的正确打开方式——Debugging
- Python24中使用urllib时遇到IOError的正确打开方式
- Xcode 的正确打开方式——Debugging
- Xcode 的正确打开方式——Debugging
- Xcode 的正确打开方式——Debugging
- Xcode 的正确打开方式——Debugging
- Pull Request的正确打开方式
- Xcode 的正确打开方式——Debugging
- Xcode 的正确打开方式——Debugging(转)
- Xcode 的正确打开方式——Debugging
- 高精度计时器QueryPerformanceCounter正确的打开方式(windows环境下)