IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring-AOP切面编程 -> 正文阅读

[Java知识库]Spring-AOP切面编程

目录

一、AOP面向切面编程

二、Spring-AOP的简单实现

1、定义被代理类的接口和实现类

2、定义一个切面:@Aspect+@Component

三、注解+切面环绕通知的使用

1、申明一个注解

2、申明接口和接口的实现类

3、设置切面,使用环绕通知-ProceedingJoinPoint

四、execution()语法定义


一、AOP面向切面编程

OOP(Object Oriented Programming ) 面向对象编程,万物皆对象!

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。

不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能

日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

?AOP切面编程:在不改变原程序的基础上为代码段增加新的功能

图解AOP思想

底层使用动态代理来实现原有代码的增强。

如果想了解动态代理,可以阅读我的这篇文章

静态代理、动态代理和GCLib代理

二、Spring-AOP的简单实现

本文使用Springboot(偷懒,不想引太多jar包,哈哈),首先Springboot实现切面编程,需要引入相关依赖

<!--切面AOP-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

1、定义被代理类的接口和实现类

被代理类,此处设置接口(主要是为了方便测试),AOP底层使用的就是JDK动代理。CGLib情况暂不考虑(即不声明接口的形式)

?接口

public interface AopService {
    void queryById(String userId);
    void deleteById(String userId);
}

实现类

@Service
public class AopServiceImpl implements AopService {
    
    @Override
    public void queryById(String id) {
        System.out.println("[AOP测试]:queryById-查询执行,参数:" + id);
    }

    @Override
    public void deleteById(String id) {
        System.out.println("[AOP测试]:deleteById-删除执行,参数:" + id);
    }
}

2、定义一个切面:@Aspect+@Component

模拟日志的前后输入和输出

@Component
@Aspect
public class Logging {
    /**
     * 前置通知
     */
    @Before("execution(* com.swadian.spring.aop.aopservice.impl..*.*(..))")
    public void beforeAdvice(){
        System.out.println("[logging]:日志前置通知.");
    }
    /**
     * 后置通知
     */
    @After("execution(* com.swadian.spring.aop.aopservice.impl..*.*(..))")
    public void afterAdvice(){
        System.out.println("[logging]:日志后置通知.");
    }
}

代码中@Before()是前置通知,@After()是后置通知。还有环绕通知,后边再做介绍。

代码中可以看到,切面的定义很简单。首先定义通知方法,然后通过execution表达式,指明需要在具体哪些方法前/后使用该通知。

测试

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class UserServiceImplTest {

    @Resource
    private AopService aopService;

    @Test
    public void findUserById() {
        aopService.queryById("888");
    }

    @Test
    public void deleteById() {
        aopService.deleteById("999");
    }
}
// 测试结果
[logging]:日志前置通知.
[AOP测试]:queryById-查询执行,参数:888
[logging]:日志后置通知.
------------------------------------
[logging]:日志前置通知.
[AOP测试]:deleteById-删除执行,参数:999
[logging]:日志后置通知.

如果不会写单元测试,请阅读我的这篇文章

编写简单的SpringBoot单元测试类

改进——引入切点

上边切面的定义中,我们对相同的execution表达式重复写了两次,为了避免这种重复代码,我们定义一个统一的切点,用来减少重复代码

@Component
@Aspect
public class Logging {
    /**
     * 申明一个切点
     */
    @Pointcut("execution(* com.swadian.spring.aop.aopservice.impl..*.*(..))")
    private void aopPointTest(){}
    /**
     * 前置通知
     */
    @Before("aopPointTest()")
    public void beforeAdvice(){
        System.out.println("[logging]:日志前置通知.");
    }
    /**
     * 后置通知
     */
    @After("aopPointTest()")
    public void afterAdvice(){
        System.out.println("[logging]:日志后置通知.");
    }
}

三、注解+切面环绕通知的使用

1、申明一个注解

切面常常配合注解一起使用,可以用来做日志记录或者权限验证

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginAnnotation {

    String loginName() default "游客";

    String role() default "";
}

2、申明接口和接口的实现类

本实验中为了方便测试,所以都申明了接口,实际情况中,注解会放在Controller层,Controller层是没有接口的,此时AOP底层使用的是CGlib动态代理

// 接口
public interface LoginService {
    public void login();
}

// 实现类
@Service
public class LoginServiceImpl implements LoginService {
    @LoginAnnotation(loginName = "sam", role = "MANAGE")
    public void login(){
        System.out.println("用户认证后,方法执行...");
    }
}

spring Aop 底层用了动态代理还是 cglib?

  • 如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
  • 如果要被代理的对象不是个实现类,那么,Spring会强制使用CGLib来实现动态代理。

3、设置切面,使用环绕通知-ProceedingJoinPoint

package com.swadian.spring.aop;

import com.swadian.spring.aop.aopservice.impl.LoginAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
@Aspect
public class LoginAspect {
    /**
     * 申明一个切点
     */
    @Pointcut("execution(* com.swadian.spring.aop.aopservice.impl..*.*(..))")
    private void aopPointTest() {
    }

    /**
     * 环绕通知
     */
    @Around("aopPointTest()")
    public void recordLoginInfo(ProceedingJoinPoint pjp) throws Throwable {
        // 首先获取ProceedingJoinPoint 签名  (方法有签名 方法名称 返回值类型 参数类型 及 参数个数)
        Signature signature = pjp.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Method method = methodSignature.getMethod();
            // 判断方法上的注解是不是权限验证注解
            if (method.isAnnotationPresent(LoginAnnotation.class)) {
                LoginAnnotation annotation = method.getAnnotation(LoginAnnotation.class);
                if (annotation.role().equals("MANAGE")) {
                    System.out.println("[AOP权限验证]:权限验证通过");
                    pjp.proceed();
                    System.out.println("[AOP权限验证]:被代理方法执行结束");
                } else {
                    System.out.println("[AOP权限验证]:该用户没有访问权限!");
                }
            }
        }
    }
}

可以看到在上面的代码中,定义通知的时候在通知方法中添加了入参:ProceedingJoinPoint。在创建环绕通知的时候,这个参数是必须写的。因为在需要在通知中使用ProceedingJoinPoint.proceed()方法调用被通知的方法。

另外,如果忘记调用proceed()方法,那么被代理方法将不会执行

测试:

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class LoginServiceImplTest {
    @Autowired
    LoginService loginService;

    @Test
    public void login() {
        loginService.login();
    }
}

// 测试结果
[AOP权限验证]:权限验证通过
用户认证后,方法执行...
[AOP权限验证]:被代理方法执行结束

四、execution()语法定义

例:定义切入点表达式 execution(* com.sample.service.impl..*.*(..))

execution()是最常用的切点函数,其语法如下所示:

?整个表达式可以分为五个部分:

  1. ?execution(): 表达式主体。
  2. ?第一个*号:表示返回类型,*号表示所有的类型。
  3. ?包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
  4. ?第二个*号:表示类名,*号表示所有的类。
  5. ?*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

注:表达式支持匹配多个

如 : "execution(* com.ws..*.*(*)) || execution(* com.db..*.*(*))";

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-28 00:11:50  更:2021-07-28 00:12:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/1 16:36:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码