java超快速入门(五):面向切面编程(AOP)

AOP原理

AOP是使用代理模式,在指定方法执行前、执行后等各个阶段进行自定义处理
下面所讲到的代理模式中,一共有两种类型,一种是基于接口的代理,一种是基于继承的代理

  • 基于接口的代理模式,需要指定目标类的接口,java类加载器将根据给定的接口生成一个动态代理类,并且在执行目标方法时,执行指定方法(如invoke方法)。因为动态代理类是由接口生成,因此如果通过代理类执行接口不存在但目标类实现了的方法,将会编译不通过,报错
  • 基于继承的代理模式,目标类不需要有接口,类加载器将动态生成继承自目标类的代理类,因此代理类拥有目标类的所有方法,cglib就是基于这种模式

AOP术语

  • target目标类:需要被代理的类。例如:UserService
  • Joinpoint连接点:所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
  • PointCut切入点:已经被增强的连接点。例如:addUser()
  • advice通知/增强,增强代码。例如:after、before
  • Weaving织入:是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
  • proxy代理类
  • Aspect切面:是切入点pointcut和通知advice的结合

实现AOP模式

手动代理模式

使用Proxy.newProxyInstance()方法进行代理

Target target = new Target()
TargetInterface proxy = (TargetInterface)Proxy.newProxyInstance(Target.class.getClassLoader(), new Class[]{TargetInterface.class}, new InvocationHandler() {
    public Object before(Object proxy, Method method, Object[] args){
        System.out.println("执行" + method.getName() + "\r\n");
        return proxy;
    }
    public Object after(Object proxy, Method method, Object[] args, Object result){
        System.out.println("执行结果" + result.toString() + "\r\n");
        return result;
    }
    public Object exceptionHandler(Object proxy, Method method, Object[] args){
        return null;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try{
            proxy = this.before(proxy, method, args);
            Object result = method.invoke(target, args);
            result = this.after(proxy, method, args, result);
            return result;
        }catch (Throwable throwable){
            return this.exceptionHandler(proxy, method, args)
        }
    }
});

cglib

cglib通过动态创建目标类的子类作为代理类来实现代理模式,cglib代理的好处是,目标类不需要实现任何接口,因为代理类是继承自目标类,因此有目标类的所有方法

先定义一个切面类

/** MyAspect.java **/
package com.sinbxeunha.josechan.aspect;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyAspect {
    public Object before(Object proxy, Method method, Object[] args, MethodProxy methodProxy){
        System.out.println("执行" + method.getName() + "\r\n");
        return null;
    }

    public Object after(Object proxy, Method method, Object[] args, MethodProxy methodProxy, Object result){
        assert result != null && result.toString() != null;
        System.out.println("执行结果:" + result.toString() + "\r\n");
        return result;
    }

    public Object exceptionHandler(Object proxy, Method method, Object[] args, MethodProxy methodProxy, Throwable throwable){
        System.out.println("结果异常:" + throwable.getMessage() + "\r\n");
        return null;
    }
}

使用cglib进行代理

//cglib代理
Target target = new Target();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
MyAspect aspect = new MyAspect();
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        try{
            //前方法
            proxy = aspect.before(proxy, method, args, methodProxy);
            //下面两句是一样的
            Object result = method.invoke(target, args);
//                    Object result = Proxy.invokeSuper(target, args);
            //后方法
            return result = aspect.after(proxy, method, args, methodProxy, result);
        }catch (Throwable throwable){
            return aspect.exceptionHandler(proxy, method, args, methodProxy, throwable);
        }
    }
});

aopalliance

aop联盟定义了一系列规范,实现这些接口就能实现切面编程
同样的我们实现切面类

/*** MyAspect.java ***/
package com.sinbxeunha.josechan.aspect;

import org.aopalliance.intercept.MethodInvocation;

public class MyAspect {
    public Object before(MethodInvocation invocation){
        System.out.println("执行" + invocation.getMethod().getName() + "\r\n");
        return null;
    }

    public Object after(Object result){
        System.out.println("执行结果:" + result.toString() + "\r\n");
        return result;
    }

    public Object exceptionHandler(Throwable throwable){
        System.out.println("结果异常:" + throwable.getMessage() + "\r\n");
        return null;
    }
}

定义通知,实现Advice接口,这里我们实现MethodInterceptor接口,该接口底层继承自Advice接口

/**MyInterceptor.java**/
package com.sinbxeunha.josechan.aspect;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;

public class MyInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        MyAspect aspect = new MyAspect();

        try{
            aspect.before(invocation);
            Object result = invocation.proceed();
            assert result != null;
            return aspect.after(result);
        }catch (Throwable throwable){
            return aspect.exceptionHandler(throwable);
        }
    }
}

创建代理对象

//工厂bean
ProxyFactoryBean bean = new ProxyFactoryBean();
//配置
AdvisedSupport config = new AdvisedSupport();
//目标类
Target target = new Target();
//通知
MyInterceptor interception = new MyInterceptor();
//设置目标类的接口
config.setInterfaces(new Class[]{TargetInterface.class});
//设置目标
config.setTarget(target);
//设置通知
config.addAdvice(interception);
//获得代理对象,链式调用 bean.获得工厂.配置工厂.获得代理对象
TargetInterface proxy = (TargetInterface) bean.getAopProxyFactory().createAopProxy(config).getProxy();

AspectJ

  • 切入点表达式
    execution(<方法的返回类型> <完整类名>.<方法名>(...<参数类型>))

  • aspectJ定义了一系列通知,并通过注解的方式指定通知的类型

  • 通知类型如下:

    • Before 前置通知,在目标方法执行前执行
    • AfterReturning 后置通知,在目标方法执行后执行
    • AfterThrowing 抛出异常通知,在目标方法抛出异常时执行
    • After 最终通知,所有流程走完后执行该通知
    • Around 环绕通知,类似于前面MyInterceptor类的invoke方法,需要手动写整个增强过程
  • 当指定了目标类的接口时,aspectj会通过接口实现代理类,否则将采用cglib的方式,继承目标类实现代理,当然,你也可以通过配置,让aspectj强制使用cglib的模式

首先先定义一个切面类

/** AspectJAspect.java **/
package com.sinbxeunha.josechan.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.weaver.tools.JoinPointMatch;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

//@Component
//@EnableAspectJAutoProxy
@Aspect
public class AspectJAspect {

    @Pointcut("execution(* com.sinbxeunha.josechan.service.*.*(..))")
    private void pointCut() {
    }

    //注解前置通知
    @Before("pointCut()")
    public Object before(JoinPoint joinPoint) {
        System.out.println("执行" + joinPoint.getSignature().toString() + "\r\n");
        return null;
    }

    //注解后置通知
    @AfterReturning(value = "pointCut()", returning = "result")
    public Object after(JoinPoint joinPoint, Object result) {
        assert result != null && result.toString() != null;
        System.out.println("执行结果:" + result.toString() + "\r\n");
        return result;
    }

    //注解抛出异常通知
    @AfterThrowing(value = "pointCut()", throwing = "throwable")
    public Object exceptionHandler(JoinPoint joinPoint, Throwable throwable) {
        System.out.println("结果异常:" + throwable.getMessage() + "\r\n");
        return null;
    }

    //注解最终通知
    @After("pointCut()")
    public Object afterAll(JoinPoint joinPoint) {
        return null;
    }

    //注解环绕通知,这个通知的增强内容相当于上面所有通知的并集
//    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        try {
            before(joinPoint);
            Object result = joinPoint.proceed();
            assert result != null;
            return after(joinPoint, result);
        } catch (Throwable throwable) {
            return exceptionHandler(joinPoint, throwable);
        } finally {
            afterAll(joinPoint);
        }
    }
}

接下来通过工厂构造代理

// AspectJ
//目标类
Target target = new Target();
//切面类
AspectJAspect aspect = new AspectJAspect();
//代理工厂
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(target);
factory.setInterfaces(new Class[]{TargetInterface.class});
factory.addAspect(aspect);
//        //强制使用cglib
//        factory.setProxyTargetClass(true);
TargetInterface proxy = factory.getProxy();

通过spring容器实现自动AspectJ代理

要通过spring容器实现自动代理,需要将切面类解析道容器中,并且开启自动代理选项
只需要在切面类上增加两行注解:

/** AspectJAspect.java **/
//...省略

@Component
@EnableAspectJAutoProxy
////强制使用cglib
//@EnableAspectJAutoProxy(proxyTargetClass = true)
@Aspect
public class AspectJAspect {
//...省略
}

并且通过容器去获取目标类

//将目标类解析到容器中
//...省略
@Service
public class Target implements TargetInterface {
//...省略
//通过容器获取目标类
/** @var ApplicationContext applicationContext 容器实例 **/
Target proxy = applicationContext.getBean(Target.class);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容