Spring中AOP的运用

Spring中的AOP(面向切面编程)运用十分广泛和方便,我们常用于日志、事务的处理,其实能够用到的地方远远不止于此,这篇文章主要就介绍AOP的用法和一些运用实例以及思路。

一、AOP常用术语

1.通知(Advice):
通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。(切面何时使用,即注有@Around、@Before、@After等注解的方法)

2.连接点(Joinpoint):
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。(在Spring中,所有的方法都可以认为是joinpoint,但是我们不希望所有的方法都添加Advice,而pointcut的作用就是提供一组规则来匹配joinpoint,给满足规则的joinpoint添加Advice。)

3.切入点(Pointcut)
通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。

4.切面(Aspect)
通知和切入点共同组成了切面:时间、地点和要发生的“故事”

5.引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)

6.目标(Target)
即被通知的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP让他做爱做的事)

7.代理(proxy)
应用通知的对象,详细内容参见设计模式里面的代理模式

8.织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术

二、AOP 常用注解

1.@Aspect:
作用:把当前类声明为切面类。

2.@Before:
作用:把当前方法看成是前置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。

3.@After
作用:把当前方法看成是始终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。

4.@Around
作用:把当前方法看成是环绕通知。包围一个连接点的通知,可以在核心方法前后完成自定义的行为。这是最常用最重要的。这个注解需要传入参数ProceedingJoinPoint pjp,决定是否进入核心方法----调用pjp.proceed();如果不调的话将不进入核心方法!
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。

5.@Pointcut
作用:指定切入点表达式
属性:
value:指定表达式的内容

6.@AfterReturning
作用:把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。

7.@AfterThrowing
作用:把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。

三、@BEFORE @AROUND @AFTER等执行顺序

@Before:核心代码执行前通知
@After:连接点执行退出时通知(不论正常返回还是异常退出)
@Around:在核心方法前后完成自定义的行为(这个注解需要传入参数ProceedingJoinPoint pjp,决定是否进入核心方法----调用pjp.proceed();如果不调的话将不进入核心方法!)
@AfterReturning:返回后通知,正常返回后通知
@AfterThrowing:返回后通知,正常返回后通知

注意:除了@Around传的参数是ProceedingJoinPoint pjp外,其它都是传的JoinPoint jp,也就是说能控制是否进入核心代码的只有Around,因为AOP走到核心代码就是通过调用ProceedingJoinPoint的proceed()方法,而JoinPoint没有这个方法

前后顺序:@Before==》@Around==》@After

四、AOP原理

动态代理(Proxy+InvocationHandler)
指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式。
简单解析动态代理:
用到Proxy+InvocationHandler。下面接用别人的代码简要说明:

public class HelloInvocationHandle implements InvocationHandler {
    private Object object;
    public HelloInvocationHandle(Object o) {
        this.object = o;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method: " + method.getName() + " is invoked");
        System.out.println("proxy: " + proxy.getClass().getName());
        Object result = method.invoke(object, args);
        // 反射方法调用
        return result;
    }
}
// HelloWorld 是一个接口,此处没有贴出来
Class<?> proxyClass = Proxy.getProxyClass(HelloWorld.class.getClassLoader(), HelloWorld.class);
Constructor cc = proxyClass.getConstructor(InvocationHandler.class);
InvocationHandler ihs = new HelloInvocationHandle(new HelloWorldImpl());
HelloWorld helloWorld = (HelloWorld) cc.newInstance(ihs);

套路就是先获取Proxy生成的class,然后获取其中使用了InvocationHandler作为参数的构造器,使用反射newInstance 实现代理对象helloWorld的生成。

当然Proxy也提供了更加方便的方法给我们使用:

final InvocationHandler in = new HelloInvocationHandle(new HelloWorldImpl());
HelloWorld helloWorld = (HelloWorld) Proxy.newProxyInstance(
    HelloWorld.class.getClassLoader(),    // 被代理对象的类加载器
    HelloWorld.class.getInterfaces(),    // 被代理对象的接口(数组,可保护多个)
    in);   // InvocationHandler实例对象

五、Spring提供了4种实现AOP的方式

1.经典的基于代理的AOP
2.@AspectJ注解驱动的切面
3.纯POJO切面
4.注入式AspectJ切面

具体可参考:
https://blog.csdn.net/J2EEWEIWEI/article/details/5844741?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

四、运用场景

AOP的使用步骤
1)将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
2)在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
3)开启基于注解的aop模式;@EnableAspectJAutoProxy

我们都知道Spring两大核心思想:DI/IOC和AOP,但要是问到你怎么用?哪里用?还是会有些含糊,这里就讲讲我理解的AOP能在那些地方用到。
(1)场景一:日志记录
(2)场景二:异常处理
(3)场景三:SQL记录,可能我们需要将执行的SQL语句,执行时间等信息记录到数据库表中,这时候AOP就能起到重要作用。
示例代码:
这里就是在执行数据库的增删改查时我们用到了AOP,其中process()方法就是封装了SQL语句以及执行时间的插入对应的表中。

@Around(value = "execution(public * org.mybatis.spring.SqlSessionTemplate.insert(..))")
    public Object insertProcess(ProceedingJoinPoint pjp) throws Throwable {
        return process(pjp);
}

@Around(value = "execution(public * org.mybatis.spring.SqlSessionTemplate.update(..))")
public Object updateProcess(ProceedingJoinPoint pjp) throws Throwable {
        return process(pjp);
}

@Around(value = "execution(public * org.mybatis.spring.SqlSessionTemplate.delete(..))")
public Object deleteProcess(ProceedingJoinPoint pjp) throws Throwable {
        return process(pjp);
}

@Around(value = "execution(public * org.mybatis.spring.SqlSessionTemplate.selectList(..))")
public Object selectListProcess(ProceedingJoinPoint pjp) throws Throwable {
        return process(pjp);
}

(4)场景四:释放Redis锁相关内容。

/**
     * 事件增删改完成后释放当前线程拥有的锁
     */
    @After("pigEventHandle() || pigBatchEventHandle() || pigRollback() || pigModify()" +
            "|| newGroupHandle() || batchNewGroupHandle() || groupEventHandle() || groupBatchEventHandle() " +
            "|| groupRollback() || groupModify()")
    public void delAllKey(final JoinPoint point){
        doctorConcurrentControl.delAll();
    }

    @Pointcut(value = "execution(* io.terminus.doctor.event.manager.DoctorPigEventManager.eventHandle(..))")
    private void pigEventHandle(){
    }

    @Pointcut(value = "execution(* io.terminus.doctor.event.manager.DoctorPigEventManager.batchEventsHandle(..))")
    private void pigBatchEventHandle(){
    }

    @Pointcut(value = "execution(Long io.terminus.doctor.event.manager.DoctorGroupManager.createNewGroup(..))")
    private void newGroupHandle(){
    }

    @Pointcut(value = "execution(Long io.terminus.doctor.event.manager.DoctorGroupManager.batchNewGroupEventHandle(..))")
    private void batchNewGroupHandle(){
    }

    @Pointcut(value = "execution(* io.terminus.doctor.event.manager.DoctorGroupEventManager.handleEvent(..))")
    private void groupEventHandle(){
    }

    @Pointcut(value = "execution(* io.terminus.doctor.event.manager.DoctorGroupEventManager.batchHandleEvent(..))")
    private void groupBatchEventHandle(){
    }

    @Pointcut(value = "execution(* io.terminus.doctor.event.manager.DoctorRollbackManager.rollbackPig(..))")
    private void pigRollback(){
    }

    @Pointcut(value = "execution(* io.terminus.doctor.event.manager.DoctorPigEventManager.modifyPigEventHandle(..))")
    private void pigModify(){
    }

    @Pointcut(value = "execution(* io.terminus.doctor.event.manager.DoctorRollbackManager.rollbackGroup(..))")
    private void groupRollback(){
    }

    @Pointcut(value = "execution(* io.terminus.doctor.event.manager.DoctorGroupEventManager.modifyGroupEventHandle(..))")
    private void groupModify(){
    }

暂时先写了些基础,后续继续更行

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值