Spring:面向切面(AOP)

Spring:面向切面(AOP)

码农世界 2024-05-26 后端 63 次浏览 0个评论

1. 代理模式

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类**间接**调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——**解耦**。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护 

Spring:面向切面(AOP) 

 

 

动态代理 

Spring:面向切面(AOP) 

Spring:面向切面(AOP) 

* 动态代理分为JDK动态代理和cglib动态代理

* 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理

* JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口

* cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类

* 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求**代理对象和目标对象实现同样的接口**(兄弟两个拜把子模式)。

* cglib:通过**继承被代理的目标类**(认干爹模式)实现代理,所以不需要目标类实现接口。

* AspectJ:是AOP思想的一种实现。本质上是静态代理,**将代理逻辑“织入”被代理的目标类编译得到的字节码文件**,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。 

 

 2. AOP概述

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率 

相关术语 

横切关注点:

这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

通知(功能):切入后要新添什么新的功能

切面:将通知(功能)封装成类

目标:被代理对象

代理:调用目标对象的功能,并且有自己的业务功能,即在目标基础上新加业务功能,但不需要修改目标对象的代码

切入点:通俗点来讲就是设置相对应的规则,满足该规则的方法就是要被代理的方法(也可理解为切入点,在哪个点(函数)切入新增功能)

 作用

* 简化代码:把方法中固定位置的重复的代码**抽取**出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。

  

* 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被**套用**了切面逻辑的方法就被切面给增强了。 

 3. 基于注解的AOP

业务场景模拟

原业务代码只能完成计算功能,在次基础上新增日志功能 

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
@Component
public class CalculatorImp implements Calculator{
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}

 语法及细节

 

/***
 * 切入点表达式:"execution(权限修饰符. 返回值类型. 全类名. 方法名(形参类型))"
 * *  可以代表任意修饰符,任意返回值类型,任意方法,任意包,任意类
 * .. 代表任意方法的形参列表为任意类型
 * 如: execution(* com.itgyl.annoAop.CalculatorImp.*(..))
 * 即修饰符和返回类型任意的com.itgyl.annoAop这个包下的CalculatorImp这个类的所有方法形参列表任意的方法 调用时会执行这个前置方法
 *     execution(public int com.itgyl.annoAop.CalculatorImp.add(int, int))
 *     即修饰符为public 返回值类型为int 的com.itgyl.annoAop包下 的 CalculatorImp类 执行add(形参为两个int类型)这个方法时会先执行这个前置方法
 */

 

/***
 * 通知:
 * 前置 @Before
 *      前置通知执行时机为代理方法执行前
 * 返回 @AfterReturning
 *      返回通知执行时机为代理方法调用结束正常返回结果后
 * 异常 @AfterThrowing
 *      异常通知执行时机为调用方法出现异常时执行
 * 后置 @After
 *      后置通知执行时机为代理方法调用完全结束后
 * 环绕 @Around
 *      环绕通知可以出现在代理方法前后中或异常等时机都能进行触发
 *      需手动调用proceed方法才会执行代理方法,并且代理方法的结果必须返回,不然报错
 */
@Aspect    //该注解声明该类是一个切面类
@Component //通过该注解可以完成自动注入,放入IoC容器中
public class LogAspect {
    /***
     * JoinPoint切入点参数,该参数可以获取执行方法的名称,方法参数等等
     */
    //@Before("execution(public int com.itgyl.annoAop.*.*(..))")
    @Before(value = "execution(public int com.itgyl.annoAop.CalculatorImp.add(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知执行啦 执行方法为 " + name + " 参数为" + Arrays.toString(args));
    }
    @After("execution(* com.itgyl.annoAop.CalculatorImp.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("后置通知执行啦 执行方法为 " + name + " 参数为" + Arrays.toString(args));
    }
    /***
     * returning:获取方法返回结果,返回结果的变量名随便取,但是下面执行的方法里的形参名要和该结果名保持一致
     * @param joinPoint 可通过该形参获取代理方法的内容
     * @param result    可通过该形参获取代理方法最终返回的结果
     */
    @AfterReturning(value = "execution(* com.itgyl.annoAop.CalculatorImp.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("返回通知执行啦 执行方法为" + name + " 参数为" + Arrays.toString(args) + "返回结果为 " + result);
    }
    /***
     * 参数Throwable为异常信息,若添加该形参也需要在切入点表达式中绑定相应的形参
     * @param joinPoint 通过该形参可以获取代理方法的参数
     * @param e         通过该形参可以获取出现异常的信息
     */
    @AfterThrowing(value = "execution(* com.itgyl.annoAop.CalculatorImp.*(..))", throwing = "e")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable e) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("异常通知执行啦 出现异常方法名为" + name + " 参数为" + Arrays.toString(args) + "异常信息为" + e);
    }
    @Around(value = "pointCut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知执行(调用代理方法前) 方法名为" + name + "参数为 " + args);
            //调用代理方法后必须返回结果
            result = joinPoint.proceed();
            System.out.println("环绕通知执行(调用代理方法后) 方法名为" + name + "参数为 " + args);
        } catch (Exception e) {
            System.out.println("环绕通知执行(调用代理方法出现异常)");
        } finally {
            System.out.println("环绕通知执行(结束)");
        }
        return result;
    }
    /***重用切入点表达式:如果要代理方法值都一样时,可以将切入点表达式封装起来,后面可以直接调用该方法不需要每次重复写executing函数
     * 细节:如果当前切面类重用该切入点表达式可以直接调用该函数使用
     *      当其他类调用时需要全类名+该函数名才能调用
     *      如:@Around(value = "pointCut()")
     *          @Around(value = "com.itgyl.annoAop.LogAspect.pointCut()")
     */
    @Pointcut(value = "execution(* com.itgyl.annoAop.CalculatorImp.*(..))")
    public void pointCut() {}
}

 测试类

public class TestAnnoAop {
    @Test
    public void testAdd() {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("bean.xml");
        System.out.println(context);
        Calculator c = context.getBean(Calculator.class);
        System.out.println(c);
        c.add(2,3);
    }
}

执行结果 

Spring:面向切面(AOP) 

切入点优先级 

 Spring:面向切面(AOP)

4. 基于XML的AOP



    
    
        
        
            
            
            
            
            
            
            
            
            
            
            
            
        
    

转载请注明来自码农世界,本文标题:《Spring:面向切面(AOP)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,63人围观)参与讨论

还没有评论,来说两句吧...

Top