Spring进阶-AOP注解开发

简介: Spring进阶-AOP注解开发

基于注解的 AOP 开发

1 快速入门

1.1 注解开发AOP制作步骤

在XML格式基础上

  • 导入坐标(伴随spring-context坐标导入已经依赖导入完成)
  • 开启AOP注解支持
  • 配置切面@Aspect
  • 定义专用的切入点方法,并配置切入点@Pointcut
  • 为通知方法配置通知类型及对应切入点@Before

1.2 注解开发AOP注意事项

1.切入点最终体现为一个方法,无参无返回值,无实际方法体内容,但不能是抽象方法

2.引用切入点时必须使用方法调用名称,方法后面的()不能省略

3.切面类中定义的切入点只能在当前类中使用,如果想引用其他类中定义的切入点使用“类名.方法名()”引用

4.可以在通知类型注解后添加参数,实现XML配置中的属性,例如after-returning后的returning属性

1.3 创建目标接口和目标类(内部有切点)

public interface TargetInterface {
    public void method();
}
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

1.4 创建切面类

public class MyAspect {
    //前置增强方法
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

1.5 将目标类和切面类的对象创建权交给 spring

@Component("target")
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}
@Component("myAspect")
public class MyAspect {
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

1.6 在切面类中使用注解配置织入关系

@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("execution(* com.itheima.aop.*.*(..))")
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

或者把切点表达式抽取出来

@Component("myAspect")
@Aspect
public class MyAspect {
    @Pointcut(""execution(* com.itheima.aop.*.*(..))"")
    public void pt(){}
    @Before("pt()")
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

1.7 在配置文类中开启组件扫描和 AOP 的自动代理

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

1.8 测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=SpringConfig.class)
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}

1.9 测试结果

2 注解通知的类型

通知的配置语法:@通知注解(“切点表达式”)

2.1 AOP注解详解

2.1.1 @Aspect

  • 名称:@Aspect
  • 类型:注解
  • 位置:类定义上方
  • 作用:设置当前类为切面类
  • 格式:
  • @Aspect
    public class AopAdvice {
    }
  • 说明:一个beans标签中可以配置多个aop:config标签

2.2.2 @Pointcut

名称:@Pointcut

类型:注解

位置:方法定义上方

作用:使用当前方法名作为切入点引用名称

格式:

@Pointcut(“execution(* *(…))”)

public void pt() {

}

说明:被修饰的方法忽略其业务功能,格式设定为无参无返回值的方法,方法体内空实现(非抽象)

2.2.3 @Before

  • 名称:@Before
  • 类型:注解
  • 位置:方法定义上方
  • 作用:标注当前方法作为前置通知
  • 格式:
    @Before(“pt()”)
    public void before(){
    }
  • 特殊参数:

2.2.4 @After

  • 名称:@After
  • 类型:注解
  • 位置:方法定义上方
  • 作用:标注当前方法作为后置通知
  • 格式:
    @After(“pt()”)
    public void after(){
    }
  • 特殊参数:

2.2.5 @AfterReturning

  • 名称:@AfterReturning
  • 类型:注解
  • 位置:方法定义上方
  • 作用:标注当前方法作为返回后通知
  • 格式:
    @AfterReturning(value=“pt()”,returning = “ret”)
    public void afterReturning(Object ret) {
    }
  • 特殊参数:
  • returning :设定使用通知方法参数接收返回值的变量名

2.2.6 @AfterThrowing

  • 名称:@AfterThrowing
  • 类型:注解
  • 位置:方法定义上方
  • 作用:标注当前方法作为异常后通知

格式:

@AfterThrowing(value=“pt()”,throwing = “t”)

public void afterThrowing(Throwable t){

}

特殊参数:

throwing :设定使用通知方法参数接收原始方法中抛出的异常对象名

2.2.7 @Around

  • 名称:@Around
  • 类型:注解
  • 位置:方法定义上方
  • 作用:标注当前方法作为环绕通知

格式:

@Around(“pt()”)

public Object around(ProceedingJoinPoint pjp) throws Throwable {

Object ret = pjp.proceed();

return ret;

}

特殊参数:

AOP使用XML配置情况下,通知的执行顺序由配置顺序决定,在注解情况下由于不存在配置顺序的概念的概念,参照通知所配置的方法名字符串对应的编码值顺序,可以简单理解为字母排序


同一个通知类中,相同通知类型以方法名排序为准

不同通知类中,以类名排序为准

使用@Order注解通过变更bean的加载顺序改变通知的加载顺序

3 企业开发经验

  • 通知方法名由3部分组成,分别是前缀、顺序编码、功能描述
  • 前缀为固定字符串,例如baidu、itheima等,无实际意义
  • 顺序编码为6位以内的整数,通常3位即可,不足位补0
  • 功能描述为该方法对应的实际通知功能,例如exception、strLenCheck

3.1 AOP注解驱动

  • 名称:@EnableAspectJAutoProxy
  • 类型:注解
  • 位置:Spring注解配置类定义上方
  • 作用:设置当前类开启AOP注解驱动的支持,加载AOP注解
  • 格式:
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

4 综合案例

4.1 案例介绍

对项目进行业务层接口执行监控,测量业务层接口的执行效率

public interface AccountService {
    void save(Account account);
    void delete(Integer id);
    void update(Account account);
    List<Account> findAll();
    Account findById(Integer id);
}

4.2 案例分析

  • 测量接口执行效率:接口方法执行前后获取执行时间,求出执行时长
  • System.currentTimeMillis( )
  • 对项目进行监控:项目中所有接口方法,AOP思想,执行期动态织入代码
  • 环绕通知
  • proceed()方法执行前后获取系统时间

4.3 案例制作步骤

  • 定义切入点(务必要绑定到接口上,而不是接口实现类上)
  • 制作AOP环绕通知,完成测量功能
  • 注解配置AOP
  • 开启注解驱动支持

4.4 案例制作核心代码

public class RunTimeMonitorAdvice {
    //拦截所有的业务层接口中查询操作的执行
    @Pointcut("execution(* com.itheima.service.*Service.find*(..))")
    public void pt(){}
    @Around("pt()")
    public Object runtimeMonitor(ProceedingJoinPoint pjp) throws Throwable {
        //获取执行签名信息
        Signature signature = pjp.getSignature();
        //通过签名获取执行类型(接口名)
        String targetClass = signature.getDeclaringTypeName();
        //通过签名获取执行操作名称(方法名)
        String targetMethod = signature.getName();
        //获取操作前系统时间beginTime
        long beginTime = System.currentTimeMillis();
        Object ret = pjp.proceed(pjp.getArgs());
        //获取操作后系统时间endTime
        long endTime = System.currentTimeMillis();
        System.out.println(targetClass+" 中 "+targetMethod+" 运行时长 "+(endTime-beginTime)+"ms");
        return ret;
    }
}

20201205174007723.png

4.5 案例后续思考与设计

  • 测量真实性
  • 开发测量是隔离性反复执行某个操作,是理想情况,上线测量差异过大
  • 上线测量服务器性能略低于单机开发测量
  • 上线测量基于缓存的性能查询要优于数据库查询测量
  • 上线测量接口的性能与最终对外提供的服务性能差异过大
  • 当外部条件发生变化(硬件),需要进行回归测试,例如数据库迁移
  • 测量结果展示
  • 测量结果无需每一个都展示,需要设定检测阈值
  • 阈值设定要根据业务进行区分,一个复杂的查询与简单的查询差异化很大
  • 阈值设定需要做独立的配置文件或通过图形工具配置(工具级别的开发)
  • 配合图形界面展示测量结果

5 代理设计模式介绍

5.1 静态代理和装饰者设计模式的区别

5.1.1 相同点

1)都要实现与目标类相同的业务接口

2)在俩个类中都要声明目标对象

3)都可以在不修改目标类的前提下增强目标方法

不同点

1)目的不同,装饰者,简单说,就是为了增强目标对象

静态代理的使用目的是为了保护和隐藏目标对象

2)对于目标对象的获取方式不同

装饰者中目标对象的获取,通过代参构造器传入,静态代理类中,是在无参构造器中直接创建。

静态代理

静态代理的思想:将被代理类作为代理类的成员,通过代理类调用被代理类的函数,并添加新的控制。包装类与被包装类实现同一接口,使得使用时的代码一致。


应用:已经有一个日志记录器LoggerSubject,需要对writeLog()函数的前后进行某些操作(如初始化、异常处理等),使用Proxy类间接调用LoggerSubject.writeLog()实现新控制操作的添加。

interface Logger {
    void writeLog();
}
// 被代理类
class LoggerSubject implements Logger{
    @Override
    public void writeLog(){
        System.out.println("writeLog by LoggerSubject");
    }
}
// 代理类
class Proxy implements Logger{
    Logger logger;
    // 与装饰者模式的主要区别位置
    // 代理模式一般要求和原来的类行为一致,因此构造函数不传入对象
    Proxy(){
        this.logger = new LoggerSubject();
    }
    @Override
    public void writeLog(){
        System.out.println("logger write before");
        logger.writeLog();
        System.out.println("logger write after");
    }
}
public class StaticProxy {
    public static void main(String []argvs){
        Logger logger = new Proxy();
        logger.writeLog();
    }
}

动态代理技术

常用的动态代理技术

JDK 代理 : 基于接口的动态代理技术

cglib 代理:基于父类的动态代理技术

JDK 的动态代理

①目标类接口

public interface TargetInterface {
    public void method();
}

②目标类

public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

③动态代理代码

Target target = new Target(); //创建目标对象
//创建代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass()
.getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
                System.out.println("前置增强代码...");
                Object invoke = method.invoke(target, args);
                System.out.println("后置增强代码...");
                return invoke;
            }
        }
);

④ 调用代理对象的方法测试

// 测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();

cglib 的动态代理

①目标类

public class Target {
    public void method() {
        System.out.println("Target running....");
    }
}

②动态代理代码

Target target = new Target(); //创建目标对象
Enhancer enhancer = new Enhancer();   //创建增强器
enhancer.setSuperclass(Target.class); //设置父类
enhancer.setCallback(new MethodInterceptor() { //设置回调
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("前置代码增强....");
        Object invoke = method.invoke(target, objects);
        System.out.println("后置代码增强....");
        return invoke;
    }
});
Target proxy = (Target) enhancer.create(); //创建代理对象

③调用代理对象的方法测试

//测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();

代理模式的选择

Spirng可以通过配置的形式控制使用的代理形式,默认使用jdkproxy,通过配置可以修改为使用cglib

false表示使用默认JDK动态代理,true表示使用cglib动态代理

XML配置

<!--XMP配置AOP-->
<aop:config proxy-target-class="false"></aop:config>

注解驱动

//注解驱动
@EnableAspectJAutoProxy(proxyTargetClass = true)

面试

AOP是什么

面向切面编程,在不修改源代码的情况下对原有功能进行增强,底层是动态代理

动态代理的分类

分为jdk和cglib,jdk动态代理只能增强某个接口的子类对象,cglib接口和类的子类对象都可以增强,spring默认是jdk代理而spring boot是cglib

在开发中经常的通知有哪些

前置通知Before,后置通知After,环绕通知Around 这三个用的比较多

要想对某个方法进行增强有几种方式

1.继承

2.装饰者

3.动态代理

目录
相关文章
|
26天前
|
人工智能 运维 Java
Spring AI Alibaba Admin 开源!以数据为中心的 Agent 开发平台
Spring AI Alibaba Admin 正式发布!一站式实现 Prompt 管理、动态热更新、评测集构建、自动化评估与全链路可观测,助力企业高效构建可信赖的 AI Agent 应用。开源共建,现已上线!
2302 40
|
2月前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
454 128
|
2月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
375 0
|
29天前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
29天前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
1月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
312 2
|
2月前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
273 12
|
2月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
268 1
使用Spring的@Retryable注解进行自动重试
|
2月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
159 0
探索Spring Boot的@Conditional注解的上下文配置
|
2月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
105 0
Spring中最大化@Lazy注解,实现资源高效利用