Spring-AOP实现


1 基于CGLib的动态代理设计

基于jdk的动态代理参考之前写的反射部分的博客9.2和10.3章节:https://blog.csdn.net/u013165358/article/details/118380631

基于jdk的动态代理要求被代理的类必须实现接口。如果没有实现接口,就要考虑使用CGLib实现动态代理。

实现方法:

1.1 配置cglib依赖

<dependency>
       <groupId>cglib</groupId>
       <artifactId>cglib</artifactId>
       <version>3.3.0</version>
</dependency>

1.2 原理与实现

CGLib主要是通过给目标类增加一个子类实现代理。代理的实现主要通过拦截器interceptor接口。通过此拦截器可以拦截目标类的所有非final方法:当调用目标类的相关方法时,首先就去执行拦截器里定义的方法,这样就相当于去动态代理了。

1.2.1 自定义拦截器(实现Interceptor接口),实现intercept方法。

package com.kkb.xzk.aop;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyMethodInterceptor implements MethodInterceptor {
    private IAOP aop;
    public MyMethodInterceptor(IAOP aop){
        this.aop = aop;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        aop.before();
        //Object invoke = method.invoke(realObject, objects);
        Object invoke = methodProxy.invokeSuper(o, objects);
        aop.after();
        return invoke;
    }
}

intercept方法的四个参数解释:

All generated proxied methods call this method instead of the original method. The original method may either be invoked by normal reflection using the Method object, or by using the MethodProxy (faster).
Params:
obj – "this", the enhanced object
method – intercepted Method
args – argument array; primitive types are wrapped
proxy – used to invoke super (non-intercepted method); may be called as many times as needed
Returns:
any value compatible with the signature of the proxied method. Method returning void will ignore this value.
Throws:
Throwable – any exception may be thrown; if so, super method will not be invoked
See Also:
MethodProxy
  • obj: 当前对象,即被增强的对象(目标对象,被代理对象)
  • method:被拦截的方法(真实业务方法)
  • args:方法的参数
  • methodProxy:用于调用真实业务的方法(通过invokeSuper)

1.2.2 通过Enhancer获得代理对象

package com.kkb.xzk.aop;

import com.kkb.xzk.service.IMessage;
import com.kkb.xzk.service.impl.NetMessage;
import net.sf.cglib.proxy.Enhancer;
import org.junit.Test;

import static org.junit.Assert.*;

public class MyMethodInterceptorTest {
    @Test
    public void Test1(){
        IMessage msg = new NetMessage();
        Enhancer enc = new Enhancer();
        enc.setSuperclass(msg.getClass());
        enc.setCallback(new MyMethodInterceptor(new TransAOP()));
        IMessage firstProxy = (IMessage) enc.create();
        firstProxy.send();
    }

}

注意:

  • 基于jdk的动态代理可以实现多级代理,但CGLib无法实现多级代理。
  • 上述代码也可通过Enhancer.create(superclass, callback)来实现。

2 SpringAOP

2.1 Spring AOP相关概念

Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

2.1.1 AOP的相关术语

  • Target(目标对象)
    要被增强的对象,一般是业务逻辑类的对象。
  • Proxy(代理)
    一个类被 AOP 织入增强后,就产生一个结果代理类。
  • Aspect(切面)
    表示增强的功能,就是一些代码完成的某个功能,非业务功能。是切入点和通知的结合。
  • Joinpoint(连接点)
    所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法(一般是类中的业务方法),因为Spring只支持方法类型的连接点。
  • Pointcut(切入点)
    切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
    被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
  • Advice(通知/增强)
    所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
    切入点定义切入的位置,通知定义切入的时间
  • Weaving(织入).
    是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
切面的三个关键因素:
1、切面的功能--切面能干啥
2、切面的执行位置--使用Pointcut表示切面执行的位置
3、切面的执行时间--使用Advice表示时间,在目标方法之前还是之后执行。

2.2 AspectJ对AOP的实现

2.2.1 导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.9</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <target>11</target>
                    <source>11</source>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.3 实现真实业务接口和业务类

package com.kkb.xzk.service;
import org.springframework.stereotype.Service;
@Service
public interface IService {
    void add(int id, String name);
    boolean update(int num);
}
package com.kkb.xzk.service.impl;

import com.kkb.xzk.service.IService;
import org.springframework.stereotype.Service;

@Service("nba")
public class NBAService implements IService {
    @Override
    public void add(int id, String name) {
        System.out.println("NBAService: add()执行。。。id=" + id + ", name="+name);
    }

    @Override
    public boolean update(int num) {
        if(num > 666){
            return true;
        }
        return false;
    }
}

2.4 注解实现切面类

2.4.0 声明spring配置文件

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.kkb.xzk"></context:component-scan>
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

</beans>

2.4.1 @Aspect声明为切面类

package com.kkb.xzk.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.lang.reflect.Modifier;

@Component
@Aspect
public class MyAspect {
    @Before("execution(* com.kkb.xzk.service.impl.*.*(..))")
    public void before(JoinPoint jp){
        System.out.println("在核心任务之前执行.......");
        System.out.println(jp.getSignature().getDeclaringType()); //class com.kkb.xzk.service.impl.NBAService
        System.out.println(jp.getSignature().getDeclaringTypeName()); //com.kkb.xzk.service.impl.NBAService
        System.out.println(jp.getSignature().getName()); //add
        System.out.println(Modifier.toString(jp.getSignature().getModifiers())); //public

    }
    @After("execution(* com.kkb.xzk.service.impl.*.*(..))")
    public void after(){
        System.out.println("在核心任务之后执行.......");
    }
    public void exception(){

    }
    public void myFinally(){
    }
}

2.4.2 @***指定切面的执行时间

@Before、@After、@AfterReturn、@AfterThrowing

AOP代理之后的方法组织:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	//@Around:ProceedingJoinPoint之前
    Object result;
    try {
        //@Before
        result = method.invoke(target, args);
        //@AfterReturning
        return result;
    } catch (InvocationTargetException e) {
        Throwable targetException = e.getTargetException();
        //@AfterThrowing
        throw targetException;
    } finally {
        //@After
    }
    //@Around:@Around:ProceedingJoinPoint之后
 }

2.4.3 切面的执行位置:@***(“execution表达式”)

  • execution定义了切面要拦截哪些方法。基本格式:修饰符 返回值 包名.类名.方法名称(参数类型) 异常抛出类型,其中返回值方法名(参数)是必须要有的。
    • *匹配任意0-n个字符
    • .. 出现在类的结尾表示只拦截当前包和当前包的子包下面的类;出现在参数类型表示匹配任意数量的参数
    • + 出现在类名后表示当前类及其子类;出现在接口后表示当前接口及其实现类
示例:
execution(* com.kkb.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.kkb.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟
“*”,表示包、子包下的所有类。

execution(* com.kkb.service.IUserService+.*(..))
指定切入点为:IUserService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,
则为该类及其子类中的任意方法
2.4.3.1 前置通知:@Before
@Before("execution(* com.kkb.xzk.service.impl.*.*(..))")
    public void before(JoinPoint jp){
        System.out.println("在核心任务之前执行.......");
        System.out.println(jp.getSignature().getDeclaringType()); //class com.kkb.xzk.service.impl.NBAService
        System.out.println(jp.getSignature().getDeclaringTypeName()); //com.kkb.xzk.service.impl.NBAService
        System.out.println(jp.getSignature().getName()); //add
        System.out.println(Modifier.toString(jp.getSignature().getModifiers())); //public

    }

可以传入JoinPoint参数,或的被拦截的方法的相关属性。(注意,连接点就等同于方法)

2.4.3.2 后置通知:@AfterReturning
@AfterReturning(value="execution(* com.kkb.xzk.service.impl.*.*(..))", returning = "result")
    public void afterReturning(Object result){
        System.out.println("在核心任务之后执行.......");
        System.out.println("方法的返回值是:" + result);
    }

可以在AfterReturning里声明returning参数,让切面方法可以拿到被代理方法的返回值。

2.4.4 切入点表达式提取execution:@Pointcut

@Component
@Aspect
public class MyAspect {
    @Pointcut("execution(* com.kkb.xzk.service.impl.*.update*(..))")
    private void pointCut1(){}

    @Pointcut("execution(* com.kkb.xzk.service.impl.*.*(..))")
    private void pointCut2(){}

    @Before("pointCut2()")
    public void before(JoinPoint jp){
        System.out.println("@Before在核心任务之前执行的切面");
    }

    @AfterReturning(value="pointCut1()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result){
        System.out.println("@AfterReturning在核心任务之后执行");
        System.out.println(jp.getSignature().getName() + "方法的返回值是:" + result);
    }

    @AfterThrowing(value="pointCut1()", throwing = "ex")
    public void exception(JoinPoint jp, Throwable ex){
        System.out.println("@AfterThrowing在异常抛出后执行");
        System.out.println(jp.getSignature().getName()+"方法发生异常:" + ex.getMessage());
    }

    @After("pointCut1()")
    public void myFinally(){
        System.out.println("@After最终执行:应该出现在finally中");
    }

    @Around("pointCut1()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("@Around环绕通知之前");
        Object proceed = pjp.proceed();
        System.out.println("@Around环绕通知之后");
        return proceed;
    }
}

2.5 通过xml实现AOP

首先,之前所有和aop相关的注解都可以删除(@Aspect、@Pointcut、@Before等等)。
在spring配置文件中可以这么写:

<aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.kkb.xzk.service.impl.*.*(..))"/>
        <aop:pointcut id="pt2" expression="execution(* com.kkb.xzk.service.impl.*.update*(..))"/>
        <aop:aspect ref="myAspect2">
            <aop:before method="before" pointcut-ref="pt1"></aop:before>
            <aop:after-returning method="afterReturning" pointcut-ref="pt2" returning="result"></aop:after-returning>
            <aop:after-throwing method="exception" pointcut-ref="pt2" throwing="ex"></aop:after-throwing>
            <aop:around method="around" pointcut-ref="pt2"></aop:around>
            <aop:after method="myFinally" pointcut-ref="pt2"></aop:after>
        </aop:aspect>
    </aop:config>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值