5.3 注解实现AOP详解_第1页
5.3 注解实现AOP详解_第2页
5.3 注解实现AOP详解_第3页
5.3 注解实现AOP详解_第4页
5.3 注解实现AOP详解_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

5.3注解实现AOP学习目标与知识图谱知识目标·理解AOP的核心思想与解决的痛点问题·掌握AOP核心术语(切面、通知、切点等)·熟练使用@AspectJ注解体系(@Aspect、@Pointcut等)·掌握切点表达式语法(execution、within等)·区分五种通知类型的执行时机与应用场景能力目标·使用纯注解方式开发完整AOP切面·编写复杂切点表达式匹配目标方法·实现环绕通知进行方法增强与性能监控·解决AOP切面优先级与参数传递问题本节定位前置基础:5.2注解实现IoC(提供AOP的容器支持)核心内容:@AspectJ注解驱动AOP开发全流程后续应用:事务管理(@Transactional)、安全控制等企业级特性AOP概念与传统开发痛点传统开发中的横切逻辑问题在分层架构中,日志记录、权限检查、事务控制等横切逻辑分散在各业务方法中,导致:代码耦合度高,横切逻辑与业务逻辑交织代码复用率低,相同逻辑重复编写维护成本高,修改需改动多个业务类违反单一职责原则@ServicepublicclassUserService{publicvoidcreateUser(Useruser){//横切逻辑:日志("创建用戶:"+user.getName());//横切逻辑:权限检查if(!hasPermission()){thrownewAccessDeniedException();}//业务逻辑userDao.insert(user);//横切逻辑:事务提交transactionMmit();}}AOP解决方案与核心价值AOP通过“横切”技术将横切逻辑抽取为切面,动态织入目标方法,实现:业务逻辑与横切逻辑分离,代码结构清晰横切逻辑复用性提高,一次定义多处使用降低维护成本,修改横切逻辑无需改动业务代码符合开闭原则,扩展功能不修改原有代码生活类比:AOP如同电影院的“字幕系统”,字幕(横切逻辑)独立于电影内容(业务逻辑),动态叠加且不影响电影本身。AOP核心术语表AOP核心术语定义与关系术语定义通俗理解切面(Aspect)封装横切逻辑的类,包含切点和通知如“日志切面”“事务切面”通知(Advice)切面中的具体横切逻辑方法(前置、后置等)如“日志记录方法”切点(Pointcut)定义哪些类的哪些方法需要被织入通知“匹配所有service包下的save*方法”连接点(JoinPoint)程序执行过程中可被AOP织入的点(如方法调用)“UserService.createUser()方法执行时”织入(Weaving)将切面应用到目标对象并创建代理对象的过程“将日志切面织入UserService”目标对象(Target)被AOP织入横切逻辑的原始对象“UserService的实例”代理对象(Proxy)织入切面后生成的对象,包含原始业务逻辑和横切逻辑“UserService的代理对象”关键关系:切面=切点+通知,即“在哪些方法(切点)执行什么横切逻辑(通知)”。@AspectJ注解体系表@AspectJ核心注解功能说明注解作用域功能描述@Aspect类标记当前类为切面类,需配合@Component注解被Spring容器扫描@Pointcut方法定义切点表达式,该方法名可作为其他通知的切点引用(方法体通常为空)@Before方法前置通知:在目标方法执行前执行@AfterReturning方法返回后通知:在目标方法正常返回后执行(异常时不执行)@AfterThrowing方法异常通知:在目标方法抛出异常后执行@After方法最终通知:在目标方法执行后执行(无论正常返回还是异常,类似finally)@Around方法环绕通知:包裹目标方法,可在执行前后自定义逻辑,拥有最大控制权@Order类/方法指定切面优先级,值越小优先级越高(解决多个切面执行顺序问题)使用要求:切面类需同时标注@Aspect和@Component(或通过@Bean注册)配置类需添加@EnableAspectJAutoProxy注解开启AOP支持方法常返仅作为点标识切点表达式语法详解execution表达式核心语法execution([修饰符]返回值类型

包名.类名.方法名(参数类型)[异常类型])通配符说明:·*:匹配任意字符(不匹配.),如*Service匹配所有以Service结尾的类。·..:匹配任意字符,用于包名(匹配子包)和参数(匹配任意参数)。·+:匹配指定类及其子类,如UserService+匹配UserService及其实现类。实战示例与其他指示器execution示例:1.匹配com.example.service包下所有类的public方法:execution(public

*com.example.service.*.*(..))2.匹配UserService中返回值为String、参数为Long的方法:execution(Stringcom.example.service.UserService.*(Long))3.匹配所有service包及其子包的方法:execution(*com.example.service...*(..))其他切点指示器:·within:按类匹配,如within(com.example.service.*)·@annotation:按方法注解匹配,如@annotation(Transactional)·args:按参数类型匹配,如args(Long,String)最佳实践:优先使用execution表达式,复杂场景可组合使用(如通知类型对比表通知类型注解执行时机能否修改返回值能否处理异常典型应用场景前置通知@Before目标方法执行前否否权限检查、参数校验返回后通知@AfterReturning目标方法正常返回后可修改返回值否日志记录、数据统计异常通知@AfterThrowing目标方法抛出异常后否可处理异常异常监控、错误报警最终通知@After目标方法执行后(无论是否异常)否否资源释放、清理操作环绕通知@Around包裹目标方法(前+后)可修改返回值可处理异常性能监控、事务控制环绕通知示例@Around("execution(*com.example.service.*.*(..))")publicObjectaround(ProceedingJoinPointjoinPoint)throwsThrowable{longstart=System.currentTimeMillis();try{Objectresult=joinPceed();//执⾏⽬标⽅法returnresult;实例项目结构项目包结构com.example├─aspect//切面类包│└─LogAspect.java//日志切面(@Aspect)├─config//配置类包│└─AppConfig.java//主配置类(@EnableAspectJAutoProxy)├─service//业务服务包│├─UserService.java//用戶服务(目标对象)│└─OrderService.java//订单服务(目标对象)└─Main.java//启动类依赖说明:需引入spring-context和spring-aspects依赖LogAspect:标注@Aspect和@Component,定义切点和通知AppConfig:标注@Configuration和@EnableAspectJAutoProxyAOP执行流程1.启动容器→扫描@Component→发现@Aspectbean(LogAspect)2.@EnableAspectJAutoProxy→开启AOP代理3.容器为目标对象→(UserService等)创建代理对象4.调用目标方法时→代理对象拦截调用→执行切面通知→执行目标方法5.目标方法执行完毕→返回结果(通知可增强结果)切面类完整代码LogAspect.java核心代码packagecom.example.aspect;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.*;importorg.springframework.stereotype.Component;@Aspect//标记为切面类@Component//注册为SpringBeanpublicclassLogAspect{//定义切点:匹配service包下所有类的所有方法@Pointcut("execution(*com.example.service.*.*(..))")publicvoidservicePointcut(){}//切点标识方法(方法体为空)//前置通知@Before("servicePointcut()")publicvoidbefore(JoinPointjoinPoint){Stringmethod=joinPoint.getSignature().getName();Object[]args=joinPointgetArgs();关键注解解析@Aspect:声明此类为切面类,Spring会识别并处理其中的通知。@Pointcut:定义切点表达式,`servicePointcut()`作为后续通知的切点引用。@Before:引用`servicePointcut()`切点,在目标方法执行前触发。@Around:通过`ProceedingJoinPoint`控制目标方法执行,计算执行时间。JoinPoint:提供目标方法信息(如方法名、参数),`ProceedingJoinPoint`为其子接口支持执行目标方法切点定义与复用技巧切点复用与组合@Aspect@ComponentpublicclassLogAspect{//基础切点:所有服务类@Pointcut("within(com.example.service.*)")publicvoidserviceLayer(){}//扩展切点:所有以save开头的⽅法@Pointcut("execution(*save*(..))")publicvoidsaveMethods(){}//组合切点:服务层的save⽅法@Pointcut("serviceLayer()&&saveMethods()")publicvoidserviceSaveMethods(){}//使⽤组合切点@Before("serviceSaveMethods()")publicvoidbeforeSave(JoinPointjoinPoint){System.out.println("保存操作:"+joinPoint.getSignature().getName());}@annotation切点与优先级自定义注解切点示例1.定义@Log注解:@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceLog{}2.切点匹配标注@Log的方法:@Pointcut("@annotation(com.example.annotatipublicvoidlogAnnotatedMethods(){}3.在目标方法使用@Log:@ServicepublicclassUserService{@LogpublicvoidcreateUser(){}}切面优先级通过@Order指定(值越小优先级越高)@Aspect@Component@Order(1)//优先级⾼于Order(2)的切⾯五种通知类型代码实现LogAspect中五种通知完整实现@Aspect@Componentpublic

class

LogAspect

{@Pointcut("execution(*com.example.service.*.*(..))")publicvoidpointcut(){}

//1.前置通知@Before("pointcut()")publicvoidbefore(JoinPointjoinPoint){Stringmethod=joinPoint.getSignature().getName();System.out.println("[Before]"+method+"执行前");}

//2.返回后通知@AfterReturning(value="pointcut()",returning="result")publicvoidafterReturning(JoinPointjoinPoint,Objectresult){System.out.println("[AfterReturning]返回值:"+result);通知执行顺序正常执行流程:@Around前→@Before→目标方法→@Around后→@After→@AfterReturning异常执行流程:@Around前→@Before→目标方法(抛出异常)→@After→@AfterThrowing注意:环绕通知必须调用ceed(),否则目标方法不会执行。异常通知仅在目标方法抛出异常时执行,返回后通知不执行。AOP配置类与代理类型AppConfig配置类代码packagecom.example.config;importorg.springframework.context.annotation.ComponentScan;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration@ComponentScan(basePackages="com.example")//扫描组件(切⾯和服务)@EnableAspectJAutoProxy(proxyTargetClass=false)//开启AOP代理,proxyTargetClass=false使⽤JDK动态代理,true使⽤CGLIBpublicclassAppConfig{//无需额外配置,@EnableAspectJAutoProxy是核⼼}@EnableAspectJAutoProxy属性proxyTargetClass:是否强制使用CGLIB代理(默认false)false:目标类实现接口时用JDK代理,否则用CGLIBtrue:所有代理都用CGLIB(可代理无接口的类)exposeProxy:是否暴露代理对象到ThreadLocal(默认false),解决自调用不增强问题JDK代理vsCGLIB代理对比特性JDK动态代理CGLIB代理原理实现目标接口继承目标类(生成子类)要求目标类必须实现接口目标类不能是final性能接口方法调用快方法调用稍慢(但创建代理快)代理对象类型接口实现类目标类的子类自调用问题目标对象内部方法调用不会触发AOP增强,需通过AopContext.currentProxy()获取代理对象调用。目标服务类代码示例UserService.javapackagecom.example.service;importorg.springframework.stereotype.Service;@ServicepublicclassUserService{publicStringcreateUser(Stringname){if(name==null){thrownewIllegalArgumentException("用戶名不能为空");}return"⽤⼾"+name+"创建成功";}publicvoidupdateUser(Longid){System.out.println("更新⽤⼾ID:"+id);}}OrderService.java与目标方法特点packagecom.example.service;importorg.springframework.stereotype.Service;@ServicepublicclassOrderService{publicintcreateOrder(LonguserId){return(int)(Math.random()*1000);//模拟订单ID}}目标方法特点·标注@Service被Spring容器管理,成为AOP目标对象·createUser可能抛出异常(测试@AfterThrowing)·包含不同参数和返回值类型,测试切点表达式匹配·无需任何修改即可被AOP增强(符合开闭原则)AOP增强效果调用这些服务方法时,LogAspect通知自动织入,实现日志记录和性能监控。运行结果与解析Main.java启动类与执行输出Main.javapackagecom.example;importcom.example.config.AppConfig;importcom.example.service.UserService;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassMain{publicstaticvoidmain(String[]args){try(AnnotationConfigApplicationContextcontext=newAnnot

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论