JavaWeb开发(三)3.7

您所在的位置:网站首页 java和javaweb有什么关系 JavaWeb开发(三)3.7

JavaWeb开发(三)3.7

2023-03-16 15:27| 来源: 网络整理| 查看: 265

一、Spring AOP(面向切面编程)概念 1、概述

AOP(Aspect Oriented Programming面向切面编程):说简单点就是我们可以在不修改源代码的情况下,对程序的方法进行增强。说复杂点就是将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。即系统级的服务从代码中解耦出来。例如:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来。允许你把遍布应用各处的功能分离出来形成可重用组件。提高程序的可重用性,同时提高了开发的效率。

在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。 所谓核心业务,工作中做的最多的就是增删改查,增删改查都叫核心业务; 所谓周边功能,比如性能统计,日志记录,事务管理等等。 周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面。在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP。

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

2、AOP的常见术语 切面 (Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象。可以理解成,就是一个特殊的类(包含的都是增强核心业务的代码),切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!连接点(join point):指那些被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。整个系统的所有方法都可以称为连接点。切入点(Pointcut):对连接点进行拦截的定义。就是被选中的连接点,可以通过execution来确定选中的连接点有哪些。通知(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能),就是切面这个类中的代码块。目标对象:代理的目标对象。引入(Introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。织入(Weaving) 把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)。 3、Spring Aop中的通知类型 前置通知(Before Advice)::在目标方法被调用前调用通知功能;相关的类 org.springframework.aop.MethodBeforeAdvice。后置通知(After Advice):在目标方法被调用之后调用通知功能;相关的类 org.springframework.aop.AfterReturningAdvice。返回通知(After-returning):在目标方法成功执行之后调用通知功能。异常通知(After-throwing):在目标方法抛出异常之后调用通知功能;相关的类org.springframework.aop.ThrowsAdvice。环绕通知(Around):把整个目标方法包裹起来,在被调用前和调用之后分别调用通知功能相关的类org.aopalliance.intercept.MethodInterceptor 4、Spring AOP的实现原理

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

(1)JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口。 (2)CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

定义普通业务组件;定义切入点,一个切入点可能横切多个业务组件;定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作。

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

spring aop实现是通过动态代理的方式实现的,动态代理避免了静态代理需要定义冗余的代理类,实现类,动态代理分为两种,第一种就是 jdk 动态代理,第二种就是cglib 动态代理,aop 实现同时采用两种代理模式。

两种动态代理的区别:

jdk动态代理模式 :采用反射的方式,只能对实现接口的类生成代理,具有加载速度快,执行效率低的特点。cglib动态代理模式:采用的asm,通过字节码形式实现,是针对类实现代理,具有加载速度慢,执行效率高的特点。 二、基于Aspectj实现切面 2.1、什么是AspectJ

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件,AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作。

2.2、基于AspectJ实现AOP 2.2.1、添加maven依赖 org.aspectj aspectjrt 1.8.9 org.apache.geronimo.bundles aspectjweaver 1.6.8_2 org.springframework spring-aop 5.2.1.RELEASE 2.2.2、切入点表达式

execution ,匹配方法的执行(常用),让spring知道对哪个类中的哪个方法进行增强。

execution(表达式) execution([权限修饰符] [返回值类型] 包名.类名.方法名(参数列表)) 全匹配方式 public void com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account) 访问修饰符可以省略 void com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account) 返回值可以使用*号,表示任意返回值 com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account) 包名可以使用号,表示任意包,有几级包,需要写几个 *.*.*.*.AccountServiceImpl.saveAccount(com.arbor.domain.Account) 使用…来表示当前包,及其子包 com..AccountServiceImpl.saveAccount(com.arbor.domain.Account) 类名可以使用*号,表示任意类 com..*.saveAccount(com.arbor.domain.Account) 方法名可以使用*号,表示任意方法 com..*.*( com.arbor.domain.Account) 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 com..*.*(*) 参数列表可以使用…表示有无参数均可,有参数可以是任意类型 com..*.*(..) 全通配方式 *..*.*(..) 通常情况下,是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类 execution(* com.arbor.service.impl.*.*(..)) 2.2.3、常用注解 @Aspect:把当前类声明为切面类;@Before:把当前方法看成是前置通知;@AfterReturning:把当前方法看成是后置通知;@AfterThrowing:把当前方法看成是异常通知;@After:把当前方法看成是最终通知。

上面四个注解的参数: value:用于指定切入点表达式,还可以指定切入点表达式的引用。

2.2.4、示例 创建一个类和方法,对该类中的方法做增强 import org.springframework.stereotype.Component; /** * @author chenqun * @date 2023/3/14 22:01 */ @Component public class QiZeKJService { public String addQiZeKJ() { System.out.println("addQiZeKJ..."); return "ok"; } } 创建增强类,在增强类中创建方法,不同的方法代表不同的通知类型,并配置不同类型的通知 /** * @author chenqun * @date 2023/3/14 22:02 */ import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect// aop 代理 public class UserProxy { /** * 前置通知 */ @Before("execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));") public void before() { System.out.println("前置通知..."); } /** * 后置通知 */ @After("execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));") public void after() { System.out.println("后置通知..."); } /** * 环绕通知 */ @Around(value = "execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知..."); System.out.println("目标方法之前开始执行..."); Object result = proceedingJoinPoint.proceed(); System.out.println("目标方法之后开始执行..."); return result; } //@AfterReturning表达后置通知/返回通知,表达方法返回结果之后执行 @AfterReturning(value = "execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));") public void afterReturning() { System.out.println("afterReturning"); } //@AfterThrowing表达异常通知 @AfterThrowing(value = "execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));") public void afterThrowing() { System.out.println("afterThrowing"); } } 创建一个Spring的配置文件,开启注解扫描和开启aop 创建测试类 import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author chenqun * @date 2023/3/14 22:05 */ public class ProxyTest { public static void main(String[] args) { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("proxy.xml"); QiZeKJService qiZeKJService = app.getBean("qiZeKJService", QiZeKJService.class); qiZeKJService.addQiZeKJ(); } } 运行结果:

在这里插入图片描述

2.2.5、不使用XML文件开启AOP注解

定义一个配置类,使用@EnableAspectJAutoProxy注解:

import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @author chenqun * @date 2023/3/14 22:54 */ @Configuration @ComponentScan(basePackages="com.qizekj.proxy") @EnableAspectJAutoProxy public class SpringConfiguration { }

定义一个配置类MyConfigofAOP,将切面类和业务逻辑类(目标方法所在类)都加入到容器中

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @author chenqun * @date 2023/3/14 23:03 */ @EnableAspectJAutoProxy @Configuration public class MyConfigofAOP { //将切面类和业务逻辑类(目标方法所在类)都加入到容器中; @Bean public QiZeKJService qiZeKJService(){ return new QiZeKJService(); } //切面类 @Bean public UserProxy userProxy(){ return new UserProxy(); } }

编写测试类:

import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author chenqun * @date 2023/3/14 22:05 */ public class ProxyTest { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigofAOP.class); QiZeKJService qiZeKJService = applicationContext.getBean(QiZeKJService.class); qiZeKJService.addQiZeKJ(); applicationContext.close(); } }

运行结果: 在这里插入图片描述

2.3、基于XML配置文件方式 2.3.1、常用配置 aop:config:声明开始aop的配置aop:aspect:用于配置切面

参数: id:给切面提供一个唯一标识 ref:引用配置好的通知类bean的id

aop:pointcut:配置切入点表达式(指定对哪些类的哪些方法进行增强)

参数: expression:定义切入点表达式 id:给切入点表达式提供一个唯一标识

aop:before:配置前置通知(指定增强的方法在切入点方法之前执行)aop:after-returning:用于配置后置通知 aop:after-throwing:用于配置异常通知 aop:after:用于配置最终通知

上面四个配置的参数: method:指定通知类中的增强方法名称 poinitcut:指定切入点表达式 ponitcut-ref:指定切入点的表达式的引用

配置文件的约束: 2.3.2、使用示例 创建一个类和方法,对该类中的方法做增强 public class Book { public void buy() { System.out.println("buy...."); } } 创建增强类,用于增强对应的方法 public class BookProxy { public void before() { System.out.println("before...."); } public void afterReturning() { System.out.println("afterReturning...."); } public void afterThrowing() { System.out.println("afterThrowing...."); } public void after() { System.out.println("after...."); } } 创建Spring的配置文件,proxy2.xml 创建测试方法 @Test public void testProxyXml() { ApplicationContext context = new ClassPathXmlApplicationContext("proxy2.xml"); Book book = context.getBean("book", Book.class); book.buy(); } 运行结果 在这里插入图片描述 2.3.3、环绕通知 aop:around:用于配置环绕通知

参数: method:通知中方法的名称 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用

spring提供的一种可以在代码中手动控制增强代码什么时候执行的方式,通常情况下,环绕通知都是独立使用的。

eg:

创建环绕通知类BookProxy2: public class BookProxy2 { public Object around(ProceedingJoinPoint pjp) { // 定义返回值,被增强的方法的返回值 Object rtVal = null; try { // 获取被增强方法的参数列表 Object[] args = pjp.getArgs(); System.out.println("环绕通知的前置通知...."); // 执行被增强的方法,并传入参数,如果确定没有返回值或者没有参数可以不写 rtVal = pjp.proceed(args); System.out.println("环绕通知的后置通知...."); } catch (Throwable e) { System.out.println("环绕通知的异常通知...."); e.printStackTrace(); } finally { System.out.println("环绕通知的最终通知...."); } // 将被增强方法的返回值返回 return rtVal; } } 创建Spring的配置文件,proxy3.xml 创建测试方法 @Test public void testProxyXml() { ApplicationContext context = new ClassPathXmlApplicationContext("proxy3.xml"); Book book = context.getBean("book", Book.class); book.buy(); } 运行结果 在这里插入图片描述


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3