写在前面
本文旨在帮助入门和了解aop的概念以及基本用法,如有错误请在评论区指出,万分感谢。
部分资料出自尚硅谷课堂笔记,改内容更好,更完善。
依赖准备
使用aop编程的话,我们需要先导入需要的依赖
- spring-context: 包含Spring的核心功能和上下文支持。
- spring-aop: 提供了面向切面编程的支持。
什么是代理?
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
举例:
杨超越是一个明星,她会唱歌和跳舞,她现在想要举办一场演唱会,那么需要场地的租用和场地搭建这个环境的准备,已经后续的拆除。此时对于这个演唱会核心的内容就是明星杨超越唱歌,跳舞,那么非核心的内容就是场地的搭建,拆除,租用等等,这时候对杨超越来说,我只会唱歌跳舞,这方面的我不会,所以此时就可以让他的经纪人去找负责对应业务的职员搭建和准备演唱会。那么这个经纪人就属于代理。核心业务是跳舞唱歌,非核心业务就是演唱会的相关筹办。 当我们需要对核心代码增加非核心业务的逻辑时可以采用代理的技术。
代理分类
静态代理
静态代理是在编译时期就已经确定的代理方式,在静态代理中,需要手动创建一个代理类,该代理类与被代理类实现相同的接口,并持有一个对被代理对象的引用。通过在代理类中调用被代理对象的方法前后加入自定义的逻辑,实现对目标对象方法的增强或控制。
动态代理
动态代理是在运行时期根据被代理对象生成代理类的方式。在动态代理中,不需要手动创建代理类,而是通过使用Java的反射机制,在运行时动态地创建代理类和代理对象。动态代理可以代理任何实现了接口的类,它通过一组接口来描述被代理类的行为,并提供了一个InvocationHandler接口的实现类来处理代理对象的所有方法调用。在方法被调用时,代理对象会将方法的调用转发给InvocationHandler的invoke()方法,并可以在此方法中添加额外的逻辑。
实现方式(动态代理)
两种方法:
- JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!
- cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!
AOP 面向切面编程
它允许开发人员分离横切关注点(cross-cutting concerns)和核心业务逻辑。横切关注点是那些存在于应用程序中多个模块中的功能,例如日志记录、性能统计、安全性和事务管理等。通过 AOP,这些横切关注点可以被模块化地定义,并且可以被动态地应用到整个应用程序中。也就是说将核心代码和非核心代码分割开。
使用场景
- AOP(面向切面编程)是一种编程范式,它通过将通用的横切关注点(如日志、事务、权限控制等)与业务逻辑分离,使得代码更加清晰、简洁、易于维护。AOP可以应用于各种场景,以下是一些常见的AOP应用场景:
- 日志记录:在系统中记录日志是非常重要的,可以使用AOP来实现日志记录的功能,可以在方法执行前、执行后或异常抛出时记录日志。
- 事务处理:在数据库操作中使用事务可以保证数据的一致性,可以使用AOP来实现事务处理的功能,可以在方法开始前开启事务,在方法执行完毕后提交或回滚事务。
- 安全控制:在系统中包含某些需要安全控制的操作,如登录、修改密码、授权等,可以使用AOP来实现安全控制的功能。可以在方法执行前进行权限判断,如果用户没有权限,则抛出异常或转向到错误页面,以防止未经授权的访问。
- 性能监控:在系统运行过程中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈并进行优化。可以使用AOP来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算方法执行时间并输出到日志中。
- 异常处理:系统中可能出现各种异常情况,如空指针异常、数据库连接异常等,可以使用AOP来实现异常处理的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志、发送邮件等)。
- 缓存控制:在系统中有些数据可以缓存起来以提高访问速度,可以使用AOP来实现缓存控制的功能,可以在方法执行前查询缓存中是否有数据,如果有则返回,否则执行方法并将方法返回值存入缓存中。
- 动态代理:AOP的实现方式之一是通过动态代理,可以代理某个类的所有方法,用于实现各种功能。
Aop名词介绍
- 切面(Aspect):横跨多个对象的关注点,如日志记录、安全控制等。切面由切点和通知组成。
- 连接点(Join Point):程序执行过程中能够插入切面的点,如方法调用、异常处理等。
- 切点(Pointcut):对连接点进行拦截的定义,如拦截某个包下的所有方法调用。
- 通知(Advice):切面在连接点处执行的操作,如在方法执行前后添加日志记录、在方法执行发生异常时处理异常等。
- 织入(Weaving):将切面应用到目标对象并创建新的代理对象的过程。
- 目标对象(Target Object):被切面织入的对象。
- 引入(Introduction):在不修改目标对象代码的情况下,为目标对象添加新的方法或属性。
- AOP代理(AOP Proxy):AOP框架创建的代理对象,用于实现AOP功能。
SpringAop实现(注解的方式)
接口内容:
package com.ssmLearn;
/**
* + - * / 运算的标准接口!
*/
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
实现类
package com.ssmLearn;
import org.springframework.stereotype.Component;
/**
* 实现计算接口,单纯添加 + - * / 实现! 掺杂其他功能!
*/
//最后必须放到ioc
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
需要实现的内容
- 在加减乘除方法中加入日志输出
- 加入异常方法
实现代码
package com.ssmLearn.Aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
//使用注解配置插入的代理目标
@Aspect
@Component
public class AchiveAop {
@Before("execution(* com.ssmLearn.CalculatorPureImpl.*(..))")
public void start(){
System.out.println("这是初始化方法");
}
@After("execution(* com.ssmLearn.CalculatorPureImpl.*(..)))")
public void end(){
System.out.println("这是结束方法");
}
@AfterThrowing(value = "execution(public int com.ssmLearn.CalculatorPureImpl.*(..))")
public void catchExection(){
System.out.println("出现异常");
}
}
代码编写
- 注解 Aspect 指示一个类是一个切面,告诉aop框架在这个位置执行切入
- 注解Component用于将这个类放到ioc容器中
- 编写具体方法逻辑,填写对应的注解内容 ,将切面表达式写入
配置类
package com.ssmLearn.config;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "com.ssmLearn")
//这个是配置的 aspect代理启动入口
@EnableAspectJAutoProxy
public class javaConfig{
}
代码实现
- Configuration标识配置类
- ComponentScan扫描包的注解并将它们注册为 Bean。
- EnableAspectJAutoProxy这个注解开启了 AspectJ 的自动代理功能。
测试类的书写
import com.ssmLearn.Calculator;
import com.ssmLearn.config.javaConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
//@SpringJUnitConfig(locations = "classpath:spring-aop.xml")
@SpringJUnitConfig(value = {javaConfig.class})
class AopTest {
@Autowired
private Calculator calculator;
@Test
public void testCalculator(){
int res = calculator.add(1,0);
System.out.println("res = " + res);
}
@Test
public void test(){
// 使用配置类 就可以直接使用这个ioc容器 直接使用后AnnotationConfigApplicationContext
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(javaConfig.class);
Calculator bean = applicationContext.getBean(Calculator.class);
int res = bean.add(1,2);
System.out.println("res = " + res);
}
}
代码实现
实现ioc容器管理需要 创建容器(根据配置类创建)->获取对象(根绝需求获得bean对象)->调用对象的方法(通过指定class文件获取)->关闭容器(避免溢出)
- 使用springJunitConfig框架进行测试,填写对应的配置类文件,使用Autowired注解实例化接口。然后使用对象执行相应的方法
- 使用AnnotationConfigApplicationContext根绝配置类创建,然后获去对象进行调用