spring基于注解声明的Aspectj

AOP描述

与基于代理类的AOP时限相比。基于xml的声明式Aspectj要便捷的多,但是它也存在这一些缺点,那就是要在spring文件中配置大量的代码信息。为了解决这个问题,Aspectj框架为AOP的实现提供了一套注解,用以取代spring配置文件中为实现AOP功能所配置的臃肿代码。

Aspectj的注解介绍及描述

在这里插入图片描述在这里插入图片描述

案例分析

用idea创建一个maven模块,项目结构如下:

在这里插入图片描述

编写要通知的对象

在java目录下创建包,首先创建接口UserDao

package com.chen.jdk;

public interface UserDao {
    public void addUser();
    public void deleteUser();
}

接在在impl目录中创建接口的实现类UserDaoImpl

package com.chen.jdk.impl;

import com.chen.jdk.UserDao;
import org.springframework.stereotype.Repository;

@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

@Repository注解

spring中的注解,@Repository用于标注数据访问组件,即DAO组件。例:

@Repository
public class VentorDaoImpl implements iVentorDao {
}

在一个稍大的项目中,如果组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找以及维护起来也不太方便。

Spring2.5为我们引入了组件自动扫描机制,他在类路径下寻找标注了上述注解的类,并把这些类纳入进spring容器中管理

它的作用和在xml文件中使用bean节点配置组件时一样的。要使用自动扫描机制。

也就是说,它所注解的类会交由spring容器管理,就像我们把对象交由spring管理一样。在spring中对象就是那些配置文件中的bean。

applicationContext配置文件中配置了UserDao实现类的bean就不用这个注解了。

编写切面通知类

package com.chen.aspectj.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 切面类,用来编写通知
 */
@Aspect
@Component
public class MyAspect {
    //定义切入点表达式
    @Pointcut("execution(* com.chen.jdk.*.*(..))")
    //使用一个返回值为void、方法体为空的方法来命名切入点
    private void myPointCut(){}
    //前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint){
        System.out.println("前置通知:模拟执行权限检查....");
        System.out.println("目标类是:"+joinPoint.getTarget());
        System.out.println(",被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
    }
    //后置通知
    @AfterReturning("myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint){
        System.out.println("后置通知:模拟日志记录...");
        System.out.println(",被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
    }
}

切入点表达式,是指的是在这个路径下的任何目录任何方法都会被通知,这里有前置通知和后置通知

编写配置

在recourses下面创建applicationContext.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"
       xmlns:context="http://www.springframework.org/schema/context"
       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
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
    <!--把对象UserDao和的创建交由spring管理-->
    <bean id="userDao" class="com.chen.jdk.impl.UserDaoImpl"/>
    <bean id="myAspect" class="com.chen.aspectj.annotation.MyAspect"/>
    <!--指定要扫描的包,让注解生效-->
    <context:component-scan base-package="com.chen"/>
    <!--启动基于注解声明式Aspectj支持-->
    <aop:aspectj-autoproxy/>
</beans>

把配置文件交给idea来管理

在这里插入图片描述

编写测试类

创建测试类

package com.chen.aspectj.annotation;

import com.chen.jdk.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAnnotationAspectj {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //1 从容器中获得内容
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        //2 执行方法
        userDao.deleteUser();
    }
}

测试结果

在这里插入图片描述

案例总结

案例中对UserDao的删除用户的方法进行前后通知。

spring中的bean可以帮我们管理对象,使用注解方式来实现aop的切面通知比jdk动态代理或者cglib代理或者xml方式实现通知要便捷得多。
首先有要通知的对象,然后有要通知的内容,即有切面类。
具体就是要通知那些方法,通知方式:前置通知还是后置通知,还是异常通知等等…
最后就是配置文件要添加,指定要扫描的包并且启动对于aspectj注解的支持。

<think>我们正在讨论基于注解AspectJ执行结果和行为。用户可能是为了调试或学习其行为。注意:SpringAOP使用了AspectJ注解风格,但底层是代理机制,而不是真正的AspectJ织入。根据引用[1]:不要被`<aop:aspectj-autoproxy/>`元素的名字误导:使用它将会创建SpringAOP代理。这里只是使用了@AspectJ风格的切面声明,但并没有涉及AspectJ运行时。因此,我们需要区分两种情况:1.使用SpringAOP(基于代理,只支持方法级别的连接点)2.使用AspectJ织入(支持构造方法、字段访问等更多连接点)用户的问题是关于“基于注解AspectJ”,所以我们需要明确:如果是在Spring中使用@AspectJ注解,实际上运行的是SpringAOP代理;如果是使用AspectJ编译器或织入器,则是完整的AspectJ。下面我们将分别讨论这两种情况下的执行结果和行为。###一、SpringAOP(使用@AspectJ注解风格)在Spring中,通过`<aop:aspectj-autoproxy/>`或`@EnableAspectJAutoProxy`启用AspectJ自动代理后,Spring会扫描带有`@Aspect`注解的Bean,并为匹配的连接点创建代理。####行为特点:1.**代理机制**:SpringAOP使用JDK动态代理(针对接口)或CGLIB(针对类)来创建代理对象。目标方法调用会被代理拦截,执行相应的通知(Advice)。2.**连接点支持**:仅支持方法执行连接点(即方法调用)。不支持构造器、字段访问等。3.**织入时机**:在运行时生成代理对象,因此不需要特殊的编译过程。4.**切面声明**:使用`@Aspect`注解标注的类,其中包含使用`@Before`,`@After`,`@Around`等注解的通知方法。####示例代码:```java@Aspect@ComponentpublicclassLoggingAspect{@Before("execution(*com.example.service.*.*(..))")publicvoidlogBefore(JoinPointjoinPoint){System.out.println("Beforemethod:"+joinPoint.getSignature().getName());}}```####执行结果:当调用`com.example.service`包下的任何方法时,会在方法执行前打印日志。###二、AspectJ织入(使用AspectJ编译器或加载时织入)如果使用完整的AspectJ,可以通过编译时织入(CTW)或加载时织入(LTW)将切面代码直接织入目标类字节码中。####行为特点:1.**织入机制**:AspectJ在编译期或类加载期修改字节码,将通知代码直接插入到连接点处。2.**连接点支持**:支持更丰富的连接点,如方法调用/执行、构造器调用/执行、字段访问、异常处理等。3.**性能**:由于是直接修改字节码,运行时没有代理的开销,性能更高。4.**切面声明**:同样使用`@Aspect`注解,但需要AspectJ运行时支持。####示例代码:与SpringAOP中的切面声明相同,但需要配置AspectJ织入器。####执行结果:切面代码被织入到目标类中,在指定的连接点处执行通知。###三、如何查看执行结果和行为1.**调试**:在调试模式下运行程序,观察在连接点处是否执行了通知逻辑。设置断点在通知方法内和目标方法内,查看调用栈。2.**日志**:在通知方法中加入日志输出,观察执行顺序。3.**查看织入后的字节码**(仅AspectJ织入):-使用反编译工具(如CFR、JD-GUI)查看织入后的类文件。-或者使用AspectJ提供的工具`ajc`编译时指定输出目录,然后查看生成的.class文件。###四、Spring中配置AspectJ加载时织入(LTW)引用[1]提到:11.8.4Load-timeweavingwithAspectJintheSpringFramework在Spring中启用AspectJLTW:1.添加依赖:`org.aspectj:aspectjweaver`2.配置上下文:启用加载时织入```xml<context:load-time-weaver/>```3.使用`@EnableLoadTimeWeaving`注解Java配置方式)4.配置切面:创建`META-INF/aop.xml`文件,指定切面####示例aop.xml:```xml<!DOCTYPEaspectjPUBLIC"-//AspectJ//DTD//EN""http://www.eclipse.org/aspectj/dtd/aspectj.dtd"><aspectj><weaver><!--指定要织入的包--><includewithin="com.example..*"/></weaver><aspects><!--指定切面类--><aspectname="com.example.aspect.LoggingAspect"/></aspects></aspectj>```###五、注意事项1.**代理与织入的区别**:-SpringAOP代理:只能拦截public方法(除非配置了CGLIB),且目标对象必须由Spring容器管理。-AspectJ织入:可以织入任何方法(包括private)、构造器、字段等,且不依赖于Spring容器。2.**执行顺序**:-在SpringAOP中,如果有多个切面,可以通过`@Order`注解或实现`Ordered`接口指定顺序。-在AspectJ中,可以使用`declareprecedence`来指定切面的优先级。###总结-如果你在Spring中使用`@AspectJ`注解,实际上使用的是SpringAOP代理,其行为是运行时通过代理拦截方法调用。-如果你使用AspectJ织入(编译时或加载时),则是通过字节码操作将通知代码直接织入目标类,支持更丰富的连接点。为了调试或学习,你可以通过日志、调试和查看字节码来观察行为。§§相关问题§§1.SpringAOPAspectJ的主要区别是什么?2.如何在Spring中配置AspectJ加载时织入(LTW)?3.如何确定一个切面是否被正确应用?4.在AspectJ中,如何指定多个切面的执行顺序?5.AspectJ支持哪些类型的连接点?</think>### AspectJ基于注解的执行结果与行为分析 AspectJ基于注解的实现主要通过`@AspectJ`风格声明切面,其执行结果和行为取决于运行时环境(Spring AOP代理 vs 完整AspectJ织入): #### 1. **Spring AOP代理模式(默认行为)** ```java @Aspect public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint jp) { System.out.println("拦截方法: " + jp.getSignature()); } } ``` **执行结果**: - 仅拦截**public方法**调用 - 通过动态代理实现,目标方法调用栈显示`$Proxy`类 - **不拦截**内部方法调用(如A类方法调用自身B方法) - **不支持**构造器/字段访问等连接点 > 引用[1]明确说明:`<aop:aspectj-autoproxy/>`实际创建的是Spring代理,非AspectJ运行时[^1]。 #### 2. **完整AspectJ织入(LTW)** 启用加载时织入: ```xml <context:load-time-weaver/> ``` ```java @Aspect public class FieldAccessAspect { @Before("set(* com.example.model.*.value)") public void logFieldSet(JoinPoint jp) { System.out.println("字段修改: " + jp.getSignature()); } } ``` **执行结果**: - 可拦截**字段赋值**、**构造器调用**等高级连接点 - 字节码直接修改目标类(无代理类) - 支持`within`等更精细的切点表达式 - 需添加`META-INF/aop.xml`定义织入范围 #### 3. **行为验证方法** - **调试检查**: ```java @AfterReturning(pointcut="execution(* *Service.*(..))", returning="result") public void inspectResult(Object result) { System.out.println("方法返回: " + result); } ``` - **织入验证**: ```java public static void main(String[] args) { System.out.println("目标类类型: " + new TargetService().getClass()); // 输出:完整AspectJ织入时显示原始类名;Spring代理显示$ProxyX } ``` #### 4. **关键行为差异** | 特性 | Spring AOP代理 | AspectJ织入 | |---------------------|----------------------|---------------------| | 连接点支持 | 仅方法执行 | 方法/构造器/字段等 | | 性能影响 | 运行时代理开销 | 编译期优化 | | 内部调用拦截 | 不支持 | 支持 | | 依赖管理 | 需Spring容器 | 独立运行 | | 配置复杂度 | 简单 | 需织入器配置 | > 引用[2]指出:Spring仅利用AspectJ注解语法,实际由容器管理切面[^2]。 #### 5. **调试建议** 1. 使用`JoinPoint`参数获取详细信息: ```java @Before("@annotation(org.springframework.transaction.annotation.Transactional)") public void inspectTransactional(JoinPoint jp) { System.out.println("事务方法: " + jp.getSignature() + " 参数: " + Arrays.toString(jp.getArgs())); } ``` 2. 通过`aop.xml`控制织入粒度: ```xml <aspectj> <weaver options="-showWeaveInfo -Xmessage:debug"/> <aspects> <aspect name="com.example.aspect.DebugAspect"/> </aspects> </aspectj> ``` 3. 使用AspectJ编译器输出织入报告: ``` ajc -outxml -outfile=META-INF/aop.xml src/*.java ``` ### 常见问题场景 - **切面未生效**:检查是否启用代理/织入,或切入点表达式匹配失败 - **重复代理**:多层代理导致通知多次触发 - **异常处理冲突**:结合`@ControllerAdvice`时注意执行顺序(引用[4])[^4] - **性能问题**:过度使用`@Around`可能引入调用栈深度开销 > 引用[5]强调:AOP实现需明确语言规范与运行环境的对应关系[^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lazy_Goat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值