Spring AOP
Spring是一个轻型容器,Spring整个系列的最核心的概念就是IOC、AOP,所以看来AOP是Spring框架中的核心之一,在应用中有着非常重要的位置,同时也是其他组件的基础。AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。
AOP的相关术语
Aspect: 切面模块本身 对哪一个方法进行怎么样的增强
Joinpoint: 连接点
- 连接点是一个应用执行过程中能够插入一个切面的点。
- 连接点可以是调用方法时、抛出异常时、甚至修改字段时
- 切面代码可以利用这些点插入到应用的正规流程中
- 程序执行过程中能够应用通知的所有点
- 因为 Spring 基于动态代理,所以 Spring 只支持方法连接点。
- Spring 缺失对字段连接点的支持,无法让我们更加细粒度的通知,例如拦截对象字段的修改
- Spring 缺失对构造器连接点支持,我发在 Bean 创建时候进行通知。
Pointcut: 切入点
- 要横切的点(在Spring中就是Pointcut,通常是一个表达式)
- 切面与目标对象间的连接点有很多,切点就是从众多的连接点中选择我们感兴趣的点将切面织入到目标代码中
Advice: 通知
- 前置通知:目标方法执行前通知,但无法阻止目标方法执行
- 后置通知:只有目标方法执行结束后才会通知,如果出现异常则无法通知
- 异常通知:只有目标方法抛出异常时通知
- 环绕通知:目标方法执行前、后通知,可以阻止方法执行,方法内部可以使用 连接点对象来获取被代理方法的所有信息;调用、屏蔽被代理方法
- 最终通知:目标方法执行结束后通知,无论是否出现异常(类似于finally块)
在此处我需要强调一下连接点与切入点怎么去区分,举个例子:比如开车经过一条高速公路,这条高速公路上有很多个出口(连接点),但是我们不会每个出口都会出去,只会选择我们需要的那个出口(切点)开出去。简单可以理解为,每个出口都是连接点,但是我们使用的那个出口才是切点。每个应用有多个位置适合织入通知,这些位置都是连接点。但是只有我们选择的那个具体的位置才是切点。
配置 SpringAOP
步骤:
- 搭建环境(导入jar包)
- 功能(建java类)
- 配置AOP
搭建环境与实现类的创建这里暂不讲解,直接讲解如何配置AOP,配置AOP有两种方式一种通过xml配置,一种通过注解的方式配置,下面我们阐述一下xml配置与注释配置是怎么配置的。
XML配置方式
1.在xml文件中,先导入AOP约束
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
2.将增强类和要被代理的目标类交给Spring管理
<bean id="calc" class="com.xxx.spring.simple.calc.CalcImpl"/>
<bean id="calcAspect" class="com.xxx.spring.simple.calc.CalcAspect"/>
3.在xml中配置标签
- 声明AOP的配置开始
<aop:config></aop:config>
- 切入点表达式
<aop:pointcut: id=" " expression=" ">
id:切入点表达式唯一标识 expression:切入点表达式,这个类里面所有的方法都将会被拦截,完整写法:类的修饰符 返回值 包名.包名…类名.方法名(参数列表)
- 类的修饰符可以省略
- 可以使用 * 号表示任意返回值
- 包名可以使用 * 号表示任意的包
- 包名可以使用…代表此包以及此包下的所有子包
- 类名可以是用 * 号表示任意的类
- 方法名可以使用 * 号代表任意方法
- 可以是用…代表任意参数
4.配置切面
<aop:aspect> </aop:aspect>
属性:ref:切面类的引用(切面类需要被spring管理)
5.在切面中配置通知(在aop:aspect标签下)
- aop:before:前置通知
- aop:after-throwing:异常通知
- aop:after-returning:后置通知(调用方法的到返回值之后)
- aop:after:最终通知
- 其标签中的属性:method:切入类中的方法名
- pointcut:切入点表达式
- pointcut-ref:引入
<aop:pointcut:>
中的切入点表达式
6.代码展示
<bean id="calc" class="com.xxx.spring.simple.calc.CalcImpl"/>
<bean id="calcAspect" class="com.xxx.spring.simple.calc.CalcAspect"/>
<aop:config>
<aop:pointcut id="all_calc_method" expression="execution(*com.lanou3g.spring.simple.calc.CalcImpl.*(..))"/>
<aop:aspect ref="calcAspect">
<aop:around method="aroundM" pointcut-ref="all_calc_method"/>
<aop:before method="beforeM" pointcut-ref="all_calc_method"/>
<aop:after-returning method="afterReturningM" pointcut-ref="all_calc_method" returning="retVal"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="all_calc_method" throwing="throwable"/>
<aop:after method="afterFinallyM" pointcut-ref="all_calc_method"/>
</aop:aspect>
</aop:config>
注解方式配置
注解共有:
@Aspect //声明切面类
@PointCut//声明切入点表达式
@Before
@AfterReturning
@Afterthrowing
@After
@Around
@Slf4j
@Configuration
@ComponentScan(basePackages = “com.xxx.spring”)
@EnableAspectJAutoProxy //开启对AOP相关注解的处理
代码展示:
切面类中:
@Slf4j
@Aspect
@Component
@Pointcut("execution(* com.xxx.spring.simple.say..*.findAll*(..))")
@Around("com.xxx.spring.GlobalPointcut.say_all_method()")
目标类中(此里面只有一个实现接口中的方法):
@Component
@Slf4j
切入点表达式:
@Pointcut("execution(* com.xxx.spring.simple.say..*.*(..))")
public void say_all_method() {}
测试类中:
@Slf4j
@Configuration
@ComponentScan(basePackages = "com.xxx.spring")
@EnableAspectJAutoProxy //开启对AOP相关注解的处理
至此两种方式的配置已经结束。
SpringAOP静态代理
1.定义一个接口
public interface IUserDao {
void save();
}
2.定义目标对象
public Class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("模拟:保存用户!");
}
}
public Class UserDaoProxy implements IUserDao{
// 代理对象,需要维护一个目标对象
private IUserDao target = new UserDao();
@Override
public void save() {
System.out.println("代理操作: 开启事务...");
target.save(); // 执行目标对象的方法
System.out.println("代理操作:提交事务...");
}
}
3.在Test类的main方法中new一个UserDaoProxy类并执行save()方法,输出结果。
代理操作: 开启事务...
模拟:保存用户!
代理操作:提交事务...
静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。