AOP面向切面编程

  • 介绍
  • 底层原理
    • 有接口情况,使用JDK动态代理
    • JDK动态代理具体实现
    • 没有接口情况,使用CGLIB动态代理
  • AOP的几个术语
  • 基于AspectJ实现注解方式的AOP操作
    • 准备
    • 实现
    • 抽取相同的切入点
    • 增强类优先级设置
  • 基于AspectJ实现XML方式的AOP操作
    • 步骤
    • 实现
  • 完全注解开发

介绍

AOP:也叫做面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗的描述就是:不通过修改源代码方式,在主干功能里面添加新功能。
    用一个登录的功能来举例:假如我们原先有一个登录功能,但现在想添加一个权限判断的功能,原始方法需要修改源代码来实现,而现在可以使用AOP来添加一个权限判断模块,达到不修改源代码就能添加新功能的目的:

底层原理

AOP的底层原理是使用了动态代理,总共有两种情况,分为有接口和没有接口:

有接口情况,使用JDK动态代理

有一个接口UserDao,里面有login()方法;有一个UserDaoImpl类实现了UserDao接口并实现了login()方法,那么JDK动态代理就是创建UserDao接口实现类代理对象。

实现方法:
    使用java.lang.reflect中的Proxy类,调用其newProxyInstance方法:


方法有三个参数:
    第一参数,类加载器
    第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
    第三参数,实现这个接口InvocationHandler,创建代理对象,写增强的部分

JDK动态代理具体实现

假设有一个接口User:

package com.jackma.spring5;public interface User {public int add(int a, int b);public String update(String id);
}

和它的一个实现类UserDaoImpl:

package com.jackma.spring5;public class UserDaoImpl implements User {@Overridepublic int add(int a, int b) {System.out.println("add方法执行了");return a+b;}@Overridepublic String update(String id) {System.out.println("update方法执行了");return id ;}
}

接下来要创建接口实现类的代理对象UserDaoProxy:

package com.jackma.spring5;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;public class JDKProxy {public static void main(String[] args) {// 创建接口实现类的代理对象Class[] interfaces = {UserDao.class};UserDaoImpl userDao = new UserDaoImpl();UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));int result = dao.add(1,2);System.out.println("add result:" + result);}
}// 创建代理对象代码
class UserDaoProxy implements InvocationHandler {// 要把创建的是谁的代理对象,要把它传递过来// 通过有参构造传递Object obj;public UserDaoProxy(Object obj){this.obj = obj;}// 增强的逻辑@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法增强之前..." + method.getName() + "传递的参数:" + Arrays.toString(args));// 被增强代码执行Object res = method.invoke(obj, args);if (method.getName() == "add"){System.out.println("这是增强add方法的代码");}if (method.getName() == "upDate"){System.out.println("这是增强upDate方法的代码");}System.out.println("方法增强之后..." + obj);return res;}
}

测试结果:

没有接口情况,使用CGLIB动态代理

有一个类User,里面有有login()方法,若按照往常想要在这个方法基础上新增功能,那么就要创建User类的子类,让它重写login()方法,然后写上增强逻辑。而在Spring的AOP中要使用CGLIB动态代理,创建当前子类的代理对象。

AOP的几个术语

  1. 连接点:类中哪些方法可以被增强,这些方法称为连接点。
  2. 切入点:实际被增强的方法,称为切入点。
  3. 通知(增强):实际增强的逻辑部分称为通知(增强),总共有五种通知:
    ①:前置通知
    ②:后置通知
    ③:环绕通知
    ④:异常通知
    ⑤:最终通知
  4. 切面:把通知应用到切入点的过程(比如:把判断权限的通知应用到Login()切入点的过程)。

基于AspectJ实现注解方式的AOP操作

AspectJ不是Spring组成部分,是独立AOP框架,但是一般把AspectJ和Spirng框架一起使用,进行AOP操作。基于AspectJ实现AOP操作有两种方式:基于 xml 配置文件实现和基于注解方式实现(推荐使用)。

准备

引入AOP相关的jar包:

切入点表达式:
    作用:知道对哪个类里面的哪个方法进行增强。
    语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]) )
    举例:

    其中*表示全部,返回类型可以省略。

实现

创建类User,对该类的方法进行增强:

// 被增强类
package com.jackma.spring5.aopanno;public class User {public void add(){System.out.println("add.....");}
}

创建增强类,编写增强逻辑,在增强类里面,创建方法,让不同方法代表不同通知类型:

package com.jackma.spring5.aopanno;// 增强类
public class UserPorxy {public void before(){   // 前置通知System.out.println("before......");}
}

进行通知的配置:

  1. 在 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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    开启注解扫描--><context:component-scan base-package="com.jackma.spring5.aopanno"></context:component-scan>
</beans>
  1. 使用注解创建User和UserProxy对象:

  2. 在增强类上面添加注解 @Aspect:

  3. 在 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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    开启注解扫描--><context:component-scan base-package="com.jackma.spring5.aopanno"></context:component-scan>
<!--    开启生成aspectj代理对象--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  1. 配置不同类型的通知(在增强类里,在作为通知的方法上添加通知类型注解,使用切入点表达式配置):
package com.jackma.spring5.aopanno;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;// 增强类
@Component
@Aspect
public class UserPorxy {// 前置通知@Before(value = "execution(* com.jackma.spring5.aopanno.User.add(..))")public void before(){System.out.println("before......");}//后置通知(返回通知)@AfterReturning(value = "execution(* com.jackma.spring5.aopanno.User.add(..))")public void afterReturning() {System.out.println("afterReturning.........");}//最终通知@After(value = "execution(* com.jackma.spring5.aopanno.User.add(..))")public void after() {System.out.println("after.........");}//异常通知@AfterThrowing(value = "execution(* com.jackma.spring5.aopanno.User.add(..))")public void afterThrowing() {System.out.println("afterThrowing.........");}//环绕通知@Around(value = "execution(* com.jackma.spring5.aopanno.User.add(..))")public void around(ProceedingJoinPoint proceedingJoinPoint) throwsThrowable {System.out.println("环绕之前.........");//被增强的方法执行proceedingJoinPoint.proceed();System.out.println("环绕之后.........");}
}

测试:

@Testpublic void test1(){ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");User user = context.getBean("user", User.class);user.add();}


若在add()方法中模拟一个异常:

测试结果:

抽取相同的切入点

从当面的增强类可以看到切入点表达式都是相同的,那么我们可以对其进行抽取:

那么就可以把各种通知的注解改成:

增强类优先级设置

若有多个增强类对同一个方法进行增强,可以设置增强类优先级,方法是在增强类上面添加注解@Order(数字类型值),数字类型值越小优先级越高。
    多创建一个增强类PersonPorxy:

package com.jackma.spring5.aopanno;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;// 增强类2
@Component
@Aspect
public class PersonPorxy {// 前置通知@Before(value = "execution(* com.jackma.spring5.aopanno.User.add(..))")public void before(){System.out.println("Person before......");}
}

设置优先级,PersonPorxy设置为1,UserPorxy设置为2:


结果:

基于AspectJ实现XML方式的AOP操作

步骤

  1. 创建两个类,增强类和被增强类,创建方法
  2. 在 spring 配置文件中创建两个类对象
  3. 在 spring 配置文件中配置切入点

实现

新建两个类,一个被增强类Book和一个增强类BookProxy:

package com.jackma.spring5.aopxml;// 被增强类
public class Book {public void Buy(){System.out.println("buy......");}
}
package com.jackma.spring5.aopxml;// 增强类
public class BookProxy {public void before(){System.out.println("before......");}
}

配置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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--创建对象--><bean id="book" class="com.jackma.spring5.aopxml.Book"></bean><bean id="bookproxy" class="com.jackma.spring5.aopxml.BookProxy"></bean><!--    配置aop的增强--><aop:config>
<!--       设置切入点--><aop:pointcut id="p" expression="execution(* com.jackma.spring5.aopxml.Book.Buy(..))"/>
<!--        配置切面--><aop:aspect ref="bookproxy">
<!--            将通知配置到具体的方法上--><aop:before method="before" pointcut-ref="p"/></aop:aspect></aop:config>
</beans>

测试:

@Testpublic void test2(){ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");Book book = context.getBean("book", Book.class);book.Buy();}

完全注解开发

创建配置类UserConfig:

package com.jackma.spring5;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
// 开启扫描
@ComponentScan(basePackages = {"com.jackma"})
// 生成代理对象
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class UserConfig {}

测试:

@Testpublic void test3(){// 完全注解ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);User user = context.getBean(User.class);user.add();}

Java之Spring5:AOP相关推荐

  1. jdk源码分析书籍 pdf_什么?Spring5 AOP 默认使用Cglib?从现象到源码深度分析

    推荐阅读: 阿里工作十年拿下P8,多亏了这些PDF陪我成长(Spring全家桶+源码解析+Redis实战等)​zhuanlan.zhihu.com 从入门到熟悉,一步一步带你了解 MySQL 中的「索 ...

  2. 推荐学java——Spring之AOP

    tips:本文首发在公众号逆锋起笔 ,本文源代码在公众号回复aop 即可查看. 什么是AOP? AOP (Aspect Orient Programming),直译过来就是 面向切面编程.AOP 是一 ...

  3. 什么是 Java 中的 AOP(面向切面编程)?如何使用它来实现横切关注点?

    AOP(Aspect-oriented programming,面向切面编程),是一种编程思想和技术,旨在将横切关注点和主业务逻辑分离,使得系统更容易扩展和维护.在 Java 中,AOP 主要通过代理 ...

  4. Java总结:Spring5框架(1)

    Spring5框架(1) 一:什么是Spring? Spring框架是由于软件开发的复杂性而创建的.Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情.然而,Spring的用 ...

  5. Java之Spring5:IOC容器

    IOC容器 什么是IOC容器 IOC容器中的Bean 小试牛刀 搭建Spring5环境 写一个HelloWorld IOC底层原理 IOC的Bean管理 基于 xml 方式创建对象 基于 xml 方式 ...

  6. Java之Spring5:JDBCTemplate

    JDBCTemplate 什么是JDBCTemplate 准备工作 添加操作 修改和删除操作 查询操作 返回某个值 返回对象 返回集合 批量添加 批量修改和删除 完整代码 什么是JDBCTemplat ...

  7. [Spring5]AOP底层原理

    AOP底层原理 1.AOP底层使用动态代理 (1)有两种情况动态代理 第一种 有接口的情况,使用JDK动态代理 a.创建接口实现类代理对象,增强类的方法 第二种 没有接口的情况,使用CGLIB动态代理 ...

  8. java 自定义注解+AOP实现日志记录

    ssm版本: 1.首先自定义一个注解,该注解有两个属性,一个是模块名,一个是操作的内容.该注解是用来修饰Service层中的方法的. 2.创建一个切面类,该切面使用@Aspect和@Component ...

  9. java @around,Spring AOP基于注解的Around通知

    是一种建议类型,可确保方法执行前后的通知可以运行. 以下是通知的语法: 语法 @Pointcut("execution(* com.yiibai.Student.getAge(..))&qu ...

最新文章

  1. java方法重载编程_学java教程之普通方法重载
  2. Druid(准)实时分析统计数据库——列存储+高效压缩
  3. qq飞车服务器维护中是什么,《QQ飞车》服务器对赛车平跑稳定性的影响攻略
  4. 道哥自述:为什么弹性安全网络将诞生最大的人工智能?
  5. mime设置 压缩html,MIME设置功能
  6. STM32 应用程序加密的一种设计方案
  7. emq auth mysql_EMQ X 认证鉴权(一)——基于 MySQL 的 MQTT 连接认证
  8. 基于Python+Django+MYSQL的校园食堂点餐管理系统
  9. IMF:央行须变得更像苹果公司以保证央行数字货币处在技术前沿
  10. linux vim 常用命令
  11. [FFmpeg] Ubuntu 16.04 安装 FFmpeg
  12. java jxdatepicker_在Java Swingx中修剪JXDatePicker
  13. 树莓派安装python3.8_在树莓派(Raspberry Pi)上编译安装更新版本的Python
  14. Arrays.sort(arr, (a, b) -> a - b)是对数组进行排序
  15. 换机潮爆发,5G手机+5G超级SIM卡成趋势
  16. 6410裸机加载linux内核,KG—Tiny6410裸机环境搭建(补充篇)
  17. 【WebStorm学生认证】如何用学生邮箱进行JetBrains学生认证
  18. iOS安装脱壳后的ipa
  19. 日本关西信息中心:LPWAN技术ZETA、LoRaWAN、SIGFOX测评分析
  20. linux之getcwd函数解析,Linux 中C语言getcwd()函数的用法

热门文章

  1. Java 基础(数据类型,转译字符)
  2. 如何才能提高自己的沟通能力
  3. Win10 安装 MongoDB4
  4. 计算机网络电缆的制作组网,计算机网络基础与组网技术
  5. python爬取手机号段(电信199号段)
  6. 毕业季,我们不说再见--​愿有前程可奔赴,亦有岁月共回首。​
  7. Java中使用递归解决不死神兔问题
  8. 后端验证 Facebook 登录是否有效
  9. 2022-2027年中国无线电高度表行业发展前景及投资战略咨询报告
  10. JAVA计算机毕业设计宠物爱心救助系统Mybatis+系统+数据库+调试部署