前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java-AOP面向切面编程

java-AOP面向切面编程

作者头像
仙士可
发布2023-02-01 14:45:06
5210
发布2023-02-01 14:45:06
举报
文章被收录于专栏:仙士可博客仙士可博客

温馨提示:

本文最后更新于 2023年01月05日,已超过 25 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

什么是AOP

AOP是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善。

面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术,利用AOP可以对业务逻辑的各个部分进行隔离,降低业务逻辑的偶尔度,提高程序可重用性和开发的效率.

场景说明

当你存在一个类,用于更新数据库数据:

代码语言:javascript
复制
package org.example;

public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}

具体实现类:

代码语言:javascript
复制
package org.example;

public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("add");//模拟新增
    }
    @Override
    public void delete() {
        System.out.println("delete");//模拟删除
    }
    @Override
    public void update() {
        System.out.println("update");//模拟更新
    }
    @Override
    public void query() {
        System.out.println("query");//模拟查询
    }
}

如果我们需要在每个数据库操作的时候,插入一条日志,或者判断是否有权限删除,该怎么写呢?

代码语言:javascript
复制
    public void add() {
        //判断权限
        System.out.println("check add perm");
        System.out.println("add");//模拟新增
        //记录日志
        System.out.println("log:add");
    }

这个时候就出现了一个问题:每个类方法都需要额外添加2行代码去进行其他操作,这样对于该类来说是一种耦合,增加了代码的复杂度,那么有没有方法能够更加方便的去实现其他逻辑呢?这个时候就需要用到AOP了 AOP提供了对代码无侵入式的写法,对类方法的执行增加前后置的操作,不需要变动原有的类,就可以实现其他额外的逻辑

AOP术语

  • join point: 拦截点,比如某个业务方法
  • point cut: join point 的表达式,表示要拦截的方法集合
  • advice: 要切入的逻辑
  • before advice:在方法前切入的逻辑
  • after advice:在方法执行后切入,抛出异常也切入
  • after returning advice:在方法执行后切入,抛出异常不切入
  • after throwing advice:在方法抛出异常后切入
  • around advice 在方法执行前后切入,可以中断或忽略原有流程的执行

织入器 通过在切面定义point cut来搜索被代理类join point,然后把需要切入的advice逻辑织入到目标对象,生成代理类

AOP实现原理

AOP可以通过几个层面来实现:

  • 编译期
  • 字节码加载前
  • 字节码加载后

根据这几个层面,有着以下几种实现机制:

静态AOP

在编译期间,切面将以字节码的形式编译到目标字节码文件中.

静态AOP可使用 aspectjrt 实现

pim.xml新增依赖:

代码语言:javascript
复制
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.12</version>
        </dependency>

新增MyAspectDeme.aj文件:

代码语言:javascript
复制
package org.example;

public aspect MyAspectJDemo {
    pointcut add():call(* MathService.add(..));

    before():add(){
        System.out.println("add方法执行后记录日志");
    }
    after():add(){
        System.out.println("add方法执行前记录日志");
    }

}

调用类:

代码语言:javascript
复制
package org.example;

public class Main {
    public static void main(String[] args) {
        MathService mathService = new MathServiceImpl();
        System.out.println(mathService.add(1, 2));
    }
}

写好之后,还需要通过 aspectjtools.jar 方式编译

file
file

在编译后,main.class将新增字节码织入点:

file
file

和正常编译的对比:

file
file

运行结果:

file
file

也可以通过注解类进行切入:

代码语言:javascript
复制
package org.example;

import org.aspectj.lang.annotation.*;

@Aspect
public class TestAspect {
    @Pointcut("execution(* MathServiceImpl.add(..))")
    public void pointCutMethod() {

    }
    @Before("pointCutMethod()")
    public void testBeforeAdvice() {
        System.out.println("test advice add before");
    }
    @After("pointCutMethod()")
    public void testAfterAdvice() {
        System.out.println("test advice add after");
    }
}

输出:

file
file

动态代理

通过jdk提供的Proxy 类,可实现原生的动态代理:

代码语言:javascript
复制
    public static void main(String[] args) {
        MathServiceImpl mathService = new MathServiceImpl();
        ClassLoader loader = mathService.getClass().getClassLoader();
        Class<?>[] interfaces = mathService.getClass().getInterfaces();
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //可以加上需要的非业务代码
                //method.getName()获取方法名
                // Arrays.asList(args)获取输入值
                System.out.println("this is " + method.getName() + " method begin with" + Arrays.asList(args));
                //method:表示代理对象要代理的方法
                //invoke:回调该函数
                //args:方法需要的参数
                Object result = method.invoke(mathService, args);//代理对象回调该方法
                return result;
            }
        };
        //先写此处方法,才可找到上述三个方法填写方式
        Object o = Proxy.newProxyInstance(loader, interfaces, h);


        MathService mathServiceProxy = (MathService) o;
        System.out.println(mathServiceProxy.add(1, 2));
    }

代码类封装:

代码语言:javascript
复制
package org.example;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyFactory {
    //被代理对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //获取代理对象
    public Object getProxy() {
        /**
         * ClassLoader loader, 被代理对象的类加载器
         * Class<?>[] interfaces, 被代理对象实现的接口
         * InvocationHandler h: 当代理对象执行被代理的方法时,会触发该对象中的invoke功能
         */
        ClassLoader loader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //可以加上需要的非业务代码
                //method.getName()获取方法名
                // Arrays.asList(args)获取输入值
                System.out.println("this is " + method.getName() + " method begin with" + Arrays.asList(args));
                //method:表示代理对象要代理的方法
                //invoke:回调该函数
                //args:方法需要的参数
                Object result = method.invoke(target, args);//代理对象回调该方法
                return result;
            }
        };
        //先写此处方法,才可找到上述三个方法填写方式
        Object o = Proxy.newProxyInstance(loader, interfaces, h);
        return o;
    }
}
代码语言:javascript
复制
package org.example;

public class Main {
    public static void main(String[] args) {
        MathServiceImpl target=new MathServiceImpl();
        ProxyFactory proxyFactory=new ProxyFactory(target);
        MathService mathServiceProxy = (MathService) proxyFactory.getProxy();
        System.out.println(mathServiceProxy.add(1, 2));
    }
}

cglib 动态代理工厂

pom.xml新增:

代码语言:javascript
复制
<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
</dependency>

代理工厂类:

代码语言:javascript
复制
package org.example;

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

import java.lang.reflect.Method;
import java.util.Arrays;

public class ProxyFactory implements MethodInterceptor {
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    //获取代理对象
    public Object getProxy(){
        Enhancer enhancer=new Enhancer();
        //指定被代理对象的父类
        enhancer.setSuperclass(target.getClass());
        //指定回调类
        enhancer.setCallback(this);
        //创建代理对象
        return enhancer.create();
    }
    //当代理对象执行代理方法时触发的方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before++++++++++++++++++++");
        //可以加上需要的非业务代码
        //method.getName()获取方法名
        // Arrays.asList(args)获取输入值
        System.out.println("this is "+method.getName()+" method begin with"+ Arrays.asList(args));
        //method:表示代理对象要代理的方法
        //invoke:回调该函数
        //args:方法需要的参数
        Object result = method.invoke(target, args);//代理对象回调该方法
        return result;
    }
}

调用和上面的一致 区别在于这个方案不需要service继承interface

spring AOP动态代理

通过spring封装的组件进行实现动态代理,新增依赖:

代码语言:javascript
复制
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.15.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.15.RELEASE</version>
        </dependency>

在原来的MathServiceImpl 类上新增@Service注解,以便于spring解析

file
file

在resources下新增spring.xml配置项

代码语言:javascript
复制
<?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
       https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--包扫描-->
    <context:component-scan base-package="org.example"/>
    <!--开启aop注解-->
    <aop:aspectj-autoproxy/>
</beans>

新增TestAspect切面类:

代码语言:javascript
复制
package org.example;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Service;

@Service
@Aspect
public class TestAspect {
    //任意返回类型 aop包下的所有类都有切面日志 使用通配符
    //第一个*:修饰符和返回值类型
    //第二个*:所有类
    //第三个*:所有方法
    @Before("execution(* org.example.*.*(..))")
    public void before() {
        System.out.println("方法执行前的日志");
    }

    @After("execution(* org.example.*.*(..))") //总会被执行,不管有没有异常
    public void after() {
        System.out.println("方法执行后的日志");
    }
}

调用main:

代码语言:javascript
复制
package org.example;

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

public class Main {
    public static void main(String[] args) {
        //从spring容器中获取
        ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
        MathService mathService = (MathService) app.getBean("mathServiceImpl");
        Double add = mathService.add(10, 5);
        System.out.println(add);
    }
}

输出:

file
file

应用场景

  • 性能测试
  • 日志记录
  • 事务
  • 权限控制
  • 等等
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是AOP
  • 场景说明
  • AOP术语
  • AOP实现原理
    • 静态AOP
      • 动态代理
        • cglib 动态代理工厂
          • spring AOP动态代理
          • 应用场景
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档