SpringAop

it2026-02-01  2

SpringAOP

概述

AOP的全称是Aspect Oriented Programming(面向切面编程)

OOP语言提供了类与类之间纵向的关系(继承、接口),而AOP补充了横向的关系(比如在不改变目标类中源代码的情况下给com.lanou.spring包下所有类中以insert和update开头的方法添加事务管理)

SpringAOP和AspectJ的区别

AspectJ是一个专门主打面向切面编程的框架。 它是使用一种特殊的语言(扩展自Java语言)来编写切面代码,后缀是.aj格式,并且需要使用专门的编译器将其编译成jvm可以运行的class文件。

SpringAOP底层也是使用了AspectJ的方案,但是在上层做了很多封装层面的工作,可以让开发人员直接使用Java代码来编写切面。并且由于使用的是标准的Java语言,所以并不需要在额外安装一个专门的编译器。但是由于开发人员直接接触的是Spring AOP,那么凡是Spring中没有实现的那些AOP功能,我们就用不了了,这种情况下只好学习原生的AspectJ。

AOP的术语

切面(Aspect)

向目标类中要插入的代码

连接点(Join Pointer)

所有可能会被切入的方法都可以称之为连接点

切入点(Pointcut)

选择某个连接点切入,将切面代码织入进去。这个连接点就叫做切入点。

织入(Weaving)

把切面代码糅合到目标代码中的过程就是织入。

通知(Advice)

通知决定了切面代码织入到目标代码中后,运行的时机(比如是在目标方法执行前,还是执行后)。

在Spring中使用AOP

基于XML方式使用

把aop的schema引入

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

创建一个切面类,并且以bean的方式配置到IOC容器中

public class MyAspect { //通知是以方法方法形式体现出的 public void afterMeth() { System.out.println("[后置通知]方法执行结束"); } public void afterReturning(Object message) { System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message); } public void afterThrowing(Throwable ex) { System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage()); } public Object aroundAdvice(ProceedingJoinPoint joinPoint) { // 连接点参数可获取到被切入方法的所有信息 String targetMethodName = joinPoint.getSignature().getName(); // 获取被切入方法的名称 System.out.println("被切入的方法名:" + targetMethodName); System.out.println("[环绕通知]方法开始执行"); Object ret = null; try { ret = joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); //throw new RuntimeException(throwable); } System.out.println("[环绕通知]方法执行结束"); return ret; } public void beforeMeth() { System.out.println("[前置通知]方法开始执行"); } } <bean id="myAspect" class="com.lanou.spring.aspect.MyAspect"/><bean id="myAspect" class="com.lanou3g.spring.MyAspect" />

使用aop:config标签配置aop(将切面、切入点、通知结合到一起)

定义切入点表达式aop:aspect 引用外部定义的切面bean配置通知,引用切入点表达式 <aop:config > <aop:pointcut id="beforeOneDay" expression="execution(* com.lanou.spring..*.oneDay(..))"/> <aop:aspect ref="myAspect"> <!-- 无论是否出现异常,只要被切入的方法开始运行,都会触发此通知 --> <aop:before method="beforeMeth" pointcut-ref="beforeOneDay"/> <!-- 无论是否出现异常,只要被切入的方法运行结束,都会触发此通知 --> <aop:after method="afterMeth" pointcut-ref="beforeOneDay"/> <!-- 被切入的方法正常返回值以后,会触发此通知 --> <aop:after-returning method="afterReturning" pointcut-ref="beforeOneDay" returning="message"/> <!-- 被切入的方法抛出异常以后,会触发此通知,并且不会触发after-returning --> <aop:after-throwing method="afterThrowing" pointcut-ref="beforeOneDay" throwing="ex"/> <!-- 可以最大限度的对被切入方法附加功能,在方法执行前、后都可以通知(无论是否出现异常) ,还可以获取到被切入方法的所有信息,包括是否调用被切入的方法 --> <!-- <aop:around method="aroundAdvice" pointcut-ref="beforeOneDay"/>--> </aop:aspect> </aop:config>

注意:

<!-- xml 方式前置通知和环绕通知,在配置文件中有使用顺序的问题,谁在前,谁先出现配置方式中若要使用环绕通知,并且想要获取返回值,要让环绕通知接收返回值若不用环绕通知,则不需考虑此问题,拦截到的方法有返回值自动返回-->

基于注解方式使用

开启AOP注解支持

方式一:注解的方式

@Configuration @ComponentScan(basePackages = "com.lanou.spring") @EnableAspectJAutoProxy //自动代理,代替了动态代理的实现 public class MyConfiguration { }

方式二:xml中开启

<aop:aspectj-autoproxy/> 定义切面类 package com.lanou.spring.aspect; @Aspect //告诉spring,这个类是一个切面; @Component public class MyAspect { //注解方式前置通知和环绕通知无顺序前后问题,总是环绕先出现,接着前置 @Before("com.lanou.spring.aspect.MyPointCut.allOneDay()") public void beforeMeth() { System.out.println("[前置通知]方法开始执行"); } @After("com.lanou.spring.aspect.MyPointCut.allOneDay()") public void afterMeth() { System.out.println("[后置通知]方法执行结束"); } @AfterReturning(value = "com.lanou.spring.aspect.MyPointCut.allOneDay()",returning = "message") public void afterReturning(Object message) { System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message); } @AfterThrowing(value = "com.lanou.spring.aspect.MyPointCut.allOneDay()",throwing = "ex") public void afterThrowing(Throwable ex) { System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage()); } @Around("com.lanou.spring.aspect.MyPointCut.allOneDay()") public void aroundAdvice(ProceedingJoinPoint joinPoint) { // 连接点参数可获取到被切入方法的所有信息 String targetMethodName = joinPoint.getSignature().getName(); // 获取被切入方法的名称 System.out.println("被切入的方法名:" + targetMethodName); System.out.println("[环绕通知]方法开始执行"); //Object ret = null; try { // ret = joinPoint.proceed(); joinPoint.proceed(); } catch (Throwable throwable) { // throwable.printStackTrace(); //捕获到异常了,外界不知道 // throw new RuntimeException(throwable); } System.out.println("[环绕通知]方法执行结束"); //return ret; } } 注意:@Aspect注解没有将bean交给ioc容器管理的功能,我们需要额外添加一个@Component注解 定义切入点

官方建议我们将所有的切入点统一定义到一个地方管理,在配置通知时通过引入的方式来使用。方便后期维护(一处修改,处处生效)

@Component public class MyPointCut { //定义一个切点,并告诉AOP什么时候启动拦截并织入对应流程; @Pointcut("execution(* com.lanou.spring.service..*.oneDay(..))") public void allOneDay(){ } } 在切面类中添加要切入的代码

参见定义切面部分

在切入的代码方法上添加通知的注解

参见定义切面部分

Jdk动态代理

实现原理:

** * jdk动态代理 使用前提是,必须有接口 * 1.目标类:接口 + 实现类 * 2.切面类 放要添加的代码(通知) * 工厂类,编写工厂生成代理 * 3.代理类 将目标类和切面类(通知)结合 ,切面 * Proxy.newProxyInstance * 参数1.loader 类加载器,动态加载类运行时创建,任何类都需要类加载器将其加载到内存。 * 一般情况:当前类.class.getClassLoader(); * * 目标类实例.getClass().getClassLoader(); * * 参数2.interfaces 代理类需要实现的所有接口 * 方式1.目标类实例.getClass.getInterfaces();注意:只能获得自己接口,不能获得父类接口 * 方式2.new Class[] {UserService.class}//接口类 * 参数3.InvocationHandler 处理类, * 接口必须进行实现类,一般采用匿名内部 * * 提供invoke方法,代理类的每一个方法执行时,都将调用一次invoke * 参数1:Object proxy :代理对象 * 参数2:Method method:代理对象当前执行方法的描述对象(反射) * 执行方法名;meThod.getName(); * 执行方法:method.invoke(对象,实际参数) * 参数3:Object[] args:方法实际参数 * */
示例代码
//接口 public interface UserService { void addUser(); void updateUser(); void deleteUser(); } //切面类 public class MyAspect { public void before(){ System.out.println("前方法"); } public void after(){ System.out.println("后方法"); } }

//工厂类 package com.lanou.spring2; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyBeanFactory { //代理类是由spring生成的,spring是容器 public static UserService createService(){ //目标类 final UserService userService = new UserServiceImpl(); //切面类 final MyAspect myAspect = new MyAspect(); //代理类 UserService proxyObj = (UserService) Proxy.newProxyInstance(MyBeanFactory.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { myAspect.before(); //执行目标类的方法 Object object = method.invoke(userService,args); myAspect.after(); return object; } }); return proxyObj; } } //测试类 public class Test { public static void main(String[] args) { testJdk(); } public static void testJdk(){ UserService userService = MyBeanFactory.createService(); userService.addUser(); userService.updateUser(); userService.deleteUser(); } }

CGLIB 动态代理

/** * Cglib可以代理实现接口的类、也可以代理未实现接口的类 * * Cglib创建代理类的原理是, 先创建一个Object,然后让这个代理类去继承被代理的类,进而继承被代理类的所有public、protected方法 ,与jdk动态代理相比,无需接口,只需目标类。 */

public class MyBeanFactory { //代理类是由spring生成的,spring是容器 public static UserServiceImpl createService() { //目标类 final UserServiceImpl userService = new UserServiceImpl(); //切面类 final MyAspect myAspect = new MyAspect(); //代理类 /** * 核心类 Enhancer * 确定父类 */ // 核心类 Enhancer类是CGLIB中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展 Enhancer enhancer = new Enhancer(); // 将被代理的对象设置成父类 enhancer.setSuperclass(userService.getClass()); //设置回调函数,设置拦截器 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { myAspect.before(); //执行目标类的方法 // Object obj = method.invoke(userService,args); //代理类对象实例调用父类方法, 方法的代理 Object obj = methodProxy.invokeSuper(o, args); myAspect.after(); return obj; } }); //创建代理 UserServiceImpl proxy = (UserServiceImpl) enhancer.create(); return proxy; } }
最新回复(0)