AOP学习笔记

本文深入探讨了AOP(面向切面编程)的概念,解释了为何需要AOP以解决代码混乱和冗余问题。通过JDK动态代理和CGLIB实现AOP,展示了如何将日志记录、权限校验等关注点从核心业务中剥离。最后,介绍了AspectJ框架及其在日志管理中的应用,阐述了切点、通知和织入等关键概念。
摘要由CSDN通过智能技术生成

​🍃作者:不染心
​🍃时间:2021/7/29

一、什么是AOP

  • **AOP: 面向切面编程:**将核心业务代码过程比作一个柱体,其他的日志记录,权限校验等就像是横切核心业务的面,这些面需要完成一些非核心的业务。
  • OOP: 面向对象编程

AOP能够比OOP更好的分离系统关注点,从而提供模块化的横切关注点。可以把一个复杂的系统看作是由多个关注点来组合实现的。
一个典型的系统可能会包括几个方面的关注点,如业务逻辑,性能,数据存储,日志和调度信息,授权,安全,线程,错误检查等,还有开发过程中的关注点,如易懂,易维护,易追查,易扩展等,这样就完成了多维到多维的映射过程。

二、为什么要AOP

🏷 日常开发问题:

代码混乱:核心业务模块与其他非核心的代码交织在一起,大大影响了代码的模块独立性能,不利于代码的维护,而且分工不明确造成代码混乱。
冗余代码:其实权限的校验,异常的处理,日志的记录可以独立在一个模块给所有的服务公用,写在一起导致代码的分散和冗余。

💥 AOP如何解决:

将日志记录、权限校验等业务代码抽取出来作为一个模块,其只关注切点,针对设点的切点进行编程。

三、与OOP之间的关系

AOP的实现技术有多种,其中与Java无缝对接的是一种称为AspectJ的技术,Spring AOP 与AspectJ 实现原理上并不完全一致,但功能上是相似的。AOP的出现确实解决外围业务代码与核心业务代码分离的问题,但它并不会替代OOP,如果说OOP的出现是把编码问题进行模块化,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。

四、实现AOP

Spring提供两种动态代理机制方式来实现AOP:JDK roxyCGLIB

面向切面框架:AspectJ

AOP核心概念:

  1. 横切关注点:对于哪些方法经行拦截,拦截后怎么处理
  2. 切面(aspect): 类是对物体特征的抽象,那么切面就是对横切关注点的抽象
  3. 连接点(joinpoint):被拦截到的方法,实际上拦截点还可以是字段或者是构造器
  4. 切入点(point cut):对连接点进行拦截的定义
  5. 通知(advice):拦截到连接点之后要执行的代码,通知通常分为:前置、后置、异常、最终、环绕通知等。
  6. 目标对象:代理的目标对象

(一)JDK Proxy

📖UserManager.java:

package com.dyl.JDKproxy;

public interface UserManager {

    void adduser(String userName, String Userpass);

    void delUser(String userName);
}

📖JdkProxy.java:

package com.dyl.JDKproxy;

public class UserManagerImpl implements UserManager{

    @Override
    public void adduser(String userName, String userPass) {
        System.out.println("添加用户名称:" + userName + " 用户密码:" + userPass);
    }

    @Override
    public void delUser(String userName) {
        System.out.println("删除用户名称:" + userName);
    }
}

📖JdkProxy.java:

package com.dyl.JDKproxy;

public class MyAspect {
    public void myBefort(){
        System.out.println("方法执行之前");
    }

    public void myAfter(){
        System.out.println("方法执行之后");
    }
}

📖JdkProxy.java:

package com.dyl.JDKproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * jdk动态代理实现InvocationHandler接口
 * Spring JDK 动态代理需要实现 InvocationHandler 接口,重写 invoke 方法,
 * 客户端使用 Java.lang.reflect.Proxy 类产生动态代理类的对象。
 *
 * 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
 * 在 net.biancheng 包下创建 UserManager(用户管理接口)、UserManagerImpl(用户管理接口实现类)、
 *                                              MyAspect(切面类)和 JdkProxy(动态代理类)。
 * 运行 SpringDemo 项目。
 */

public class JdkProxy implements InvocationHandler {
    private Object target;  //需要代理的对象
    final MyAspect myAspect = new MyAspect();


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        myAspect.myBefort();
        Object result = method.invoke(target, args);
        myAspect.myAfter();

        return null;
    }

    // 获取代理对象方法
    private Object getJDKProxy(Object targetObject){

        // 为目标对象target赋值
        this.target = targetObject;

        // JDK动态代理只能代理实现了接口的类,从newProxyInstance函数所需的参数就可以看出来
        // getInterfaces() 获取这个对象的所有接口
        // getClassLoader() 将class文件加载到内存中编程class对象
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }

    public static void main(String[] args) {
        JdkProxy jdkProxy = new JdkProxy();
        UserManager userManager = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());
        userManager.adduser("dyl", "123456");
        userManager.delUser("dyl");
    }
}

📖结果:

方法执行之前
添加用户名称:dyl 用户密码:123456
方法执行之后
方法执行之前
删除用户名称:dyl
方法执行之后

Process finished with exit code 0

(二)CDLIB Proxy

📖UserManager.java:同上

📖UserManagerImpl.java:同上

📖MyAspect.java:同上

📖CGLIBProxy.java​

package com.dyl.CGLIB;



import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。使用 CGLIB 需要导入 CGLIB 和 ASM 包,即 asm-x.x.jar 和 CGLIB-x.x.x.jar 。如果您已经导入了 Spring 的核心包 spring-core-x.x.x.RELEASE.jar,就不用再导入 asm-x.x.jar 和 cglib-x.x.x.jar 了。
 *
 * 步骤如下:
 * 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
 * 导入相关 JAR 包。
 * 在 net.biancheng 包下创建 UserManager(用户管理接口)、UserManagerImpl(用户管理接口实现类)、MyAspect(切面类)和 CGLIBProxy(动态代理类)。
 * 运行 SpringDemo 项目。
 */


public class CGLIBProxy implements MethodInterceptor {

    private Object target;
    final MyAspect myAspect = new MyAspect();

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        myAspect.myBefort();
        Object invoke = method.invoke(target, objects); // 方法执行,参数:target目标对象 arr参数数组
        myAspect.myAfter();

        return invoke;
    }

    // 动态获取代理对象方法
    public Object getCgliProxy(Object objectTarget){
        this.target = objectTarget;
        Enhancer enhancer = new Enhancer();
        // 设置父类,因为CGLIB是正对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(objectTarget.getClass());
        enhancer.setCallback(this); // 设置回调
        Object result = enhancer.create(); // 创建并返回代理对象
        return result;
    }

    public static void main(String[] args) {
        CGLIBProxy cglibProxy = new CGLIBProxy();
        UserManager userManager = (UserManager) cglibProxy.getCgliProxy(new UserManagerImpl()); // 获取代理对象

        userManager.adduser("dyl", "123456");
        userManager.delUser("dyl");
    }
}

(三)AspectJ 框架

  1. AspectJ是使用面向切面的一个框架
  2. 它扩展了Java语言(它本身也是一种语言)
  3. 支持原生Java代码 有自己的编译器
  4. 将代码翻译成Java字节码文件
  5. 是为了方便编写AOP代码而出现的
  6. 使用AOP编程的三个重点 通知 切点 织入

接下来做一个AOP的日志例子,对Man实体操作,对其中指定的方法记录日志

📖 Man.java

package com.dyl.AspectJ_learn;

public class Man {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Man{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void throwException() {
        System.out.println("抛出异常");
        throw new IllegalArgumentException();
    }
}

📖 日志切面类 Logging.java

package com.dyl.AspectJ_learn;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect
public class Logging {
    /**
     * 定义切入点:该方法无方法体,主要是为了方便同类中其他方法使用此处配置的切入点
     * PointCut 的目的就是提供一种方法使得开发者能够选择自己感兴趣的 JoinPoint。
     */
    @Pointcut("execution(* com.dyl.AspectJ_learn.Man.*(..))")
    private void selectAll() {
    }
    /**
     * 前置通知:在方法执行前执行,如果通知抛出异常,阻止方法运行(应用:各种校验)
     * 使用在方法Aspect()上注册的切入点,同时接受JoinPoint切入点对象,可以没有该参数
     */
    @Before("selectAll()")
    public void beforeAdvice() {
        System.out.println("前置通知");
    }
    /**
     * 后置通知
     */
    @After("selectAll()")
    public void afterAdvice() {
        System.out.println("后置通知");
    }
    /**
     * 返回后通知:(应用:常规数据处理)
     * 方法正常返回后执行,如果方法中抛出异常,通知无法执行     *
     * 必须在方法执行后才执行,所以可以获得方法的返回值
     */
    @AfterReturning(pointcut = "selectAll()", returning = "retVal")
    public void afterReturningAdvice(Object retVal) {
        System.out.println("返回值为:" + retVal.toString());
    }
    /**
     * 环绕通知:(应用:十分强大,可以做任何事情)
     */
    @Around("selectAll()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前");
        Object result = pjp.proceed();
        System.out.println("环绕后");
        return result;
    }
    /**
     * 抛出异常通知
     */
    @AfterThrowing(pointcut = "selectAll()", throwing = "ex")
    public void afterThrowingAdvice(IllegalArgumentException ex) {
        System.out.println("这里的异常为:" + ex.toString());
    }

}

📖 resources -> Beans_AspectJ.xml

<?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-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy />
    <bean id="man" class="com.dyl.AspectJ_learn.Man">
        <property name="name" value="dyl"/>
        <property name="age" value="234"/>
    </bean>

    <bean id="logging" class="com.dyl.AspectJ_learn.Logging"/>
</beans>

🏷 Main.java

package com.dyl.AspectJ_learn;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans_AspectJ.xml");
        Man man = (Man) context.getBean("man");
        man.getAge();
        man.getName();
//        man.throwException();
    }
}

📖 结果:

环绕前
前置通知
环绕后
后置通知
返回值为:234
环绕前
前置通知
环绕后
后置通知
返回值为:dyl

💥切点配置说明:

//配置com.bao.User下的add()
execution(* com.bao.User.add(..))

//配置com.bao.User下的所有方法
execution(* com.bao.User.*(..))

//配置所有包下的所有方法
execution(**.*(..))

//配置所有包下的a开头方法
execution(* a*(..))

//配置包下的所有类的所有方法
execution(* com.service.*.*(..)))

五、AOP应用

🔖 日志记录

🔖 权限校验

🔖 返回值操作

🔖 事务

🔖 安全等

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不染心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值