1. JAVA总结系列——基础
949 2023-04-03 03:13:40
AOP(Aspect Oriented Programming) 面向切面编程,是目前软件开发中的一个热点,是Spring框架内容,利用AOP可以对业务逻辑的各个部分隔离,从而使的业务逻辑各部分的耦合性降低,提高程序的可重用性,提升开发效率。
AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。那么动态代理是如何实现将切面逻辑(advise)织入到目标类方法中去的呢?下面我们就来详细介绍并实现AOP中用到的两种动态代理。AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
AOP是处理一些横切行问题。这些横切性问题不会影响到主逻辑的实现,但是会散落到代码的各个部分,难以维护。AOP就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。
OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程的某个步骤或阶段,以获得逻辑过程的中各部分之间低耦合的隔离效果。这两种设计思想在目标上有着本质的差异。
通过下面的图可以清晰的理解AOP与OOP的区别:
AOP中的Joinpoint可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中我们可以在上面的这些Joinpoint上织入我们自定义的Advice,但是在Spring中却没有实现上面所有的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.2.4.RELEASE</version></dependency>
定义一个通用接口,所有实现此接口的类都有一个咸鱼方法和一个测试aop的方法
public interface HelloWorld { void saltedFish(); void testPrintTime();}
实现1
public class HelloWorldImpl1 implements HelloWorld{ @Override public void saltedFish() { System.out.println("this is a salted fish =========== 1"); } @Override public void testPrintTime() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("testPrintTime 1=============111111111"); }}
实现2
public class HelloWorldImpl2 implements HelloWorld{ @Override public void saltedFish() { System.out.println("this is a salted fish =========== 2"); } @Override public void testPrintTime() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("testPrintTime 2=============22222222"); }}
定义一个Advice,实现在连接点之前之后该干的事
public class TimeHandler implements MethodBeforeAdvice, AfterReturningAdvice { Long before = 0L; @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { Long after = System.currentTimeMillis(); System.out.println("==========代理后time, " + after + " ======= 间隔: " + (after - before) + "=========="); } @Override public void before(Method method, Object[] objects, Object o) throws Throwable { before = System.currentTimeMillis(); System.out.println("==========代理前time:" + 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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 定义 --> <bean id="h1" class="com.example.demo.aop.HelloWorldImpl1"></bean> <bean id="h2" class="com.example.demo.aop.HelloWorldImpl2"></bean> <!-- 定义advice --> <bean id="timeHandler" class="com.example.demo.aop.TimeHandler"></bean> <!-- 定义point cut --> <bean id="timePointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*testPrintTime"></property> </bean> <!-- 切面 关联切入点与通知 --> <bean id="timeHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="timeHandler"></property> <property name="pointcut" ref="timePointCut"></property> </bean> <!-- 设置代理--> <bean id="proxy1" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 代理的对象 --> <property name="target" ref="h1"></property> <!-- 使用的切面 --> <property name="interceptorNames" value="timeHandlerAdvisor"></property> <!-- 代理接口 --> <property name="interfaces" value="com.example.demo.aop.HelloWorld"></property> </bean> <!-- 设置代理--> <bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 代理的对象 --> <property name="target" ref="h2"></property> <!-- 使用的切面 --> <property name="interceptorNames" value="timeHandlerAdvisor"></property> <!-- 代理接口 --> <property name="interfaces" value="com.example.demo.aop.HelloWorld"></property> </bean></beans>
测试类
public class AOPTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("./application.xml"); HelloWorld helloWorld1 = (HelloWorld) applicationContext.getBean("proxy1"); HelloWorld helloWorld2 = (HelloWorld) applicationContext.getBean("proxy2"); helloWorld1.saltedFish(); System.out.println("---------------------"); helloWorld1.testPrintTime(); System.out.println("======================="); helloWorld2.saltedFish(); System.out.println("---------------------"); helloWorld2.testPrintTime(); }}
测试结果
this is a salted fish =========== 1---------------------==========代理前time:1582477901748===========testPrintTime 1=============111111111==========代理后time, 1582477902750 ======= 间隔: 1002=================================this is a salted fish =========== 2---------------------==========代理前time:1582477902750===========testPrintTime 2=============22222222==========代理后time, 1582477903250 ======= 间隔: 500==========
对于将纯POJO申明成切面的方式中,如果不使用@AspectJ,那么就需要使用使用繁琐的XML配置,因此Spring借鉴了AspectJ的切面,以提供注解驱动的AOP,但是本质上依然是使用的SpringAop的动态代理的方式,只是变成模型几乎与AspectJ完全一样。
要在 Springboot中声明 AspectJ 切面, 需在 IOC 容器中将切面声明为 Bean 实例 即加入@Component 注解;当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理。在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类。
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>
配置类
@Aspect@Configuration // 一定要加此注解,或者在入口引入public class AopLogger { /** * 标识这个方法是个前置通知, 切点表达式表示执行任意类的任意方法. * 第一个 * 代表匹配任意修饰符及任意返回值, * 第二个 * 代表任意类的对象, * 第三个 * 代表任意方法, * 参数列表中的 .. 匹配任意数量的参数 * * @param joinPoint */ @Before("execution(* com.example.demo.aop..*.*(..))") public void before(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); Object result = Arrays.asList(joinPoint.getArgs()); System.out.printf("method name is: %s, args is: %s%n", methodName, result); } @After("execution (* com.example.demo.aop..*.*(..))") public void after(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("after log method name is: " + methodName); } // @AfterReturning(value = "execution(**.*(..))", returning = "result") @AfterReturning(value = "execution (* com.example.demo.aop..*.*(..))", returning = "result") public void afterReturn(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.printf("method name is: %s, and the result is: %s%n", methodName, result); } // @AfterThrowing(value = "execution(**.*(..))", throwing = "e") @AfterThrowing(value = "execution (* com.example.demo.aop..*.*(..))", throwing = "e") public void afterThrow(JoinPoint joinPoint, Exception e) { String methodName = joinPoint.getSignature().getName(); System.out.printf("method name is: %s, and the exception is: %s%n", methodName, e); } // @Around("execution(**.*(..))") @Around("execution (* com.example.demo.aop..*.*(..))") public Object around(ProceedingJoinPoint joinPoint) { StopWatch stopWatch = new StopWatch(); String name = joinPoint.getSignature().getName(); System.out.println("===============↓↓↓↓↓↓↓↓ " + name + " ↓↓↓↓↓↓↓↓================="); stopWatch.start(); try { return joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } finally { stopWatch.stop(); System.out.println("===============↑↑↑↑↑↑↑↑ " + name + " ↑↑↑↑↑↑↑↑================="); } return null; }}
测试方法
@Testpublic void testAspectJ() { helloWorldImpl1.saltedFish(); helloWorldImpl2.testPrintTime();}
测试结果
===============↓↓↓↓↓↓↓↓ saltedFish ↓↓↓↓↓↓↓↓=================method name is: saltedFish, args is: []this is a salted fish =========== 1===============↑↑↑↑↑↑↑↑ saltedFish ↑↑↑↑↑↑↑↑=================after log method name is: saltedFishmethod name is: saltedFish, and the result is: null===============↓↓↓↓↓↓↓↓ testPrintTime ↓↓↓↓↓↓↓↓=================method name is: testPrintTime, args is: []testPrintTime 2=============22222222===============↑↑↑↑↑↑↑↑ testPrintTime ↑↑↑↑↑↑↑↑=================after log method name is: testPrintTimemethod name is: testPrintTime, and the result is: null