SpringAop概念

SpringAop的本质就是动态代理

在Spring源码中,用到的动态代理主要有两种,JDK动态代理以及CGLib动态代理。两者主要区别是:

  • 基于接口代理(JDK代理)
    基于接口代理,凡是类的方法非public修饰,或者用了static关键字修饰,那这些方法都不能被Spring AOP增强
  • 基于CGLib代理(子类代理)

目标对象没有实现接口,则默认会采用CGLIB代理。如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库)
基于子类代理,凡是类的方法使用了private、static、final修饰,那这些方法都不能被Spring AOP增强

JDK代理

静态代理模式代码:

public interface PersonService {

    void addPerson();

    void delete();

}

class  PersonServiceImpl implements PersonService{

    @Override
    public void addPerson() {
        System.out.println("生成一个人");
    }

    @Override
    public void delete() {
        System.out.println("删除一个人");
    }
}

class PesonServiceImplProxy implements  PersonService{

    // 注入目标对象 实际就是真正实现接口的实现类
    private PersonServiceImpl personService;

    public PesonServiceImplProxy(PersonServiceImpl personService){
        this.personService  = personService;
    }

    /**
     * 代理对象不会改变原有目标对象的方法 只会在生成的代理对象增强原来已有的方法的表现形式
     * @author zhangjun
     * @datetime  2019/6/6 16:03
     * @param
     * @return
     */
    @Override
    public void addPerson() {
        // before 增强方法
        System.out.println("before");

        // 还是要调用目标方法来执行原有逻辑
        personService.addPerson();

        // after增强方法
        System.out.println("after");

    }

    @Override
    public void delete() {
                                                    
    }
}

接口实现动态代理代码:

public interface PersonService {

    void addPerson();

    void delete();

}

class  PersonServiceImpl implements PersonService{

    @Override
    public void addPerson() {
        System.out.println("生成一个人");
    }

    @Override
    public void delete() {
        System.out.println("删除一个人");
    }
}

class JdkDynmicProxy implements InvocationHandler{

    private PersonServiceImpl personService;

    public JdkDynmicProxy(PersonServiceImpl personService){
        this.personService = personService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        Object invoke = method.invoke(personService, args);
        System.out.println("after");
        return invoke;
    }
}

class Client {
    public static void main(String[] args) throws Exception{
        PersonService o = (PersonService)Proxy.newProxyInstance(PersonService.class.getClassLoader(),
                new Class[]{PersonService.class},
                new JdkDynmicProxy(new PersonServiceImpl()));
        o.addPerson();
    }
}

<font color="red">动态代理比静态代理的好处是,当接口添加方法动态代理不需要改变代码,而静态代理需要手动添加新增的代码</font>

CGLib代理模式实现

代码示例

public interface PersonService {

    void addPerson();

    void delete();

}

class  PersonServiceImpl implements PersonService{

    @Override
    public void addPerson() {
        System.out.println("生成一个人");
    }

    @Override
    public void delete() {
        System.out.println("删除一个人");
    }
}
class CglibProxy implements MethodInterceptor{

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("增强的方法");
        Object invoke = method.invoke(o, objects);
        return invoke;
    }
}

class ClientProxy{
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PersonServiceImpl.class);
        enhancer.setCallback(new CglibProxy());
        PersonService o = (PersonService)enhancer.create();
        o.addPerson();
    }
}

相同点:

两种动态代理本质上都是:字节码组装,主要还是通过反射的逻辑

AOP动态代理的应用场景:

  • 日志
  • 事务
  • 权限
  • 缓存
  • 懒加载

可以简单认为如果一个类上使用了aspect就是切面,其中aspect由pointcut和advice组成。

advice介绍

由 aspect 添加到特定的 join point(即满足 point cut 规则的 join point) 的一段代码.
许多 AOP框架, 包括 Spring AOP, 会将 advice 模拟为一个拦截器(interceptor), 并且在 join point 上维护多个 advice, 进行层层拦截.
例如 HTTP 鉴权的实现, 我们可以为每个使用 RequestMapping 标注的方法织入 advice, 当 HTTP 请求到来时, 首先进入到 advice 代码中, 在这里我们可以分析这个 HTTP 请求是否有相应的权限, 如果有, 则执行 Controller, 如果没有, 则抛出异常. 这里的 advice 就扮演着鉴权拦截器的角色了.

advice的类型

  • before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
  • after return advice, 在一个 join point 正常返回后执行的 advice
  • after throwing advice, 当一个 join point 抛出异常后执行的 advice
  • after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
  • around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.

连接点(join point)

程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理.在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点.

切点(point cut)

<font color = "red">在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.</font>

introduction

为一个类型添加额外的方法或字段. Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现)

<font color="red">目标对象(Target)</font>

织入 advice 的目标对象. 目标对象也被称为 advised object.因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object)</br>
<font color = "red">注意:adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类.</font>

声明pointcut

一个 pointcut 的声明由两部分组成:

  • 一个方法签名, 包括方法名和相关参数

  • 一个 pointcut 表达式, 用来指定哪些方法执行是我们感兴趣的(即因此可以织入 advice).

在@AspectJ 风格的 AOP 中, 我们使用一个方法来描述 pointcut, 即:

@Pointcut("execution(* com.xys.service.UserService.*(..))") // 切点表达式
private void dataAccessOperation() {} // 切点前面

<font color= "red">上面我们简单地定义了一个 pointcut, 这个 pointcut 所描述的是: 匹配所有在包 com.xys.service.UserService 下的所有方法的执行.</font>

within

匹配特定包下的所有 join point, 例如 within(com.xys.) 表示 com.xys 包中的所有连接点, 即包中的所有类的所有方法. 而 within(com.xys.service.Service) 表示在 com.xys.service 包中所有以 Service 结尾的类的所有的连接点.

this 与 target(<font color = "red">不理解需要重新梳理</font>)

this 的作用是匹配一个 bean, 这个 bean(Spring AOP proxy) 是一个给定类型的实例(instance of). 而 target 匹配的是一个目标对象(target object, 即需要织入 advice 的原始的类), 此对象是一个给定类型的实例(instance of)。


常见的切点表达式

匹配 bean 名字为指定值的 bean 下的所有方法, 例如:
bean(*Service) // 匹配名字后缀为 Service 的 bean 下的所有方法
bean(myService) // 匹配名字为 myService 的 bean 下的所有方法
args

匹配参数满足要求的的方法.
例如:

@Pointcut("within(com.xys.demo2.*)")
public void pointcut2() {
}

@Before(value = "pointcut2()  &&  args(name)")
public void doSomething(String name) {
    logger.info("---page: {}---", name);
}
@Service
public class NormalService {
    private Logger logger = LoggerFactory.getLogger(getClass());

    public void someMethod() {
        logger.info("---NormalService: someMethod invoked---");
    }


    public String test(String name) {
        logger.info("---NormalService: test invoked---");
        return "服务一切正常";
    }
}

当 NormalService.test 执行时, 则 advice doSomething 就会执行, test 方法的参数 name 就会传递到 doSomething 中.

常用例子
// 匹配只有一个参数 name 的方法
@Before(value = "aspectMethod()  &&  args(name)")
public void doSomething(String name) {
}

// 匹配第一个参数为 name 的方法
@Before(value = "aspectMethod()  &&  args(name, ..)")
public void doSomething(String name) {
}

// 匹配第二个参数为 name 的方法
Before(value = "aspectMethod()  &&  args(*, name, ..)")
public void doSomething(String name) {
}
@annotation

匹配由指定注解所标注的方法, 例如:

@Pointcut("@annotation(com.xys.demo1.AuthChecker)")
public void pointcut() {
}
匹配方法签名
// 匹配指定包中的所有的方法
execution(* com.xys.service.*(..))

// 匹配当前包中的指定类的所有方法
execution(* UserService.*(..))

// 匹配指定包中的所有 public 方法
execution(public * com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法
execution(public int com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法
execution(public int com.xys.service.*(String name, ..))
匹配类型签名
// 匹配指定包中的所有的方法, 但不包括子包
within(com.xys.service.*)

// 匹配指定包中的所有的方法, 包括子包
within(com.xys.service..*)

// 匹配当前包中的指定类中的方法
within(UserService)


// 匹配一个接口的所有实现类中的实现的方法
within(UserDao+)
匹配 Bean 名字
// 匹配以指定名字结尾的 Bean 中的所有方法
bean(*Service)
切点表达式组合
// 匹配以 Service 或 ServiceImpl 结尾的 bean
bean(*Service || *ServiceImpl)

// 匹配名字以 Service 结尾, 并且在包 com.xys.service 中的 bean
bean(*Service) && within(com.xys.service.*)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • 基本知识 其实, 接触了这么久的 AOP, 我感觉, AOP 给人难以理解的一个关键点是它的概念比较多, 而且坑爹...
    永顺阅读 8,058评论 5 114
  • 因为工作需求,自己去了解一下aop并做下的记录,当然大部分都是参考他人博客以及官方文档。 目录 [关于 AOP](...
    forip阅读 2,266评论 1 20
  • IoC 容器 Bean 的作用域 自定义作用域实现 org.springframework.beans.facto...
    Hsinwong阅读 2,445评论 0 7
  • AOP 简介 在学习 Spring AOP 之前,我们先来了解一下 AOP。我们都听过面向对象编程(OOP),那么...
    樱木天亥阅读 894评论 0 3
  • 定义 官方解释 AOP,Aspect Oriented Programming的缩写,意为面向切面编程,通过预编译...
    一凡呀阅读 1,523评论 0 3