实现原理
Spring AOP实现机制是采用的动态代理和字节码生成技术实现的;AspectJ采用的是用编译器将横切逻辑织入到目标对象,动态代理和字节码生成技术是在运行期间为目标对象生成一个代理对象而将横切逻辑织入到这个代理对象中,系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象。
Spring AOP和AspectJ的区别
Spring AOP 基于动态代理方式实现;AspectJ 基于静态代理方式实现,因为上面已经提到实际上aspectj是由编译器完成,也就是说它是在编译期完成的增强;而Spring AOP是通过动态代理和字节码生成技术实现,这两个都是动态插入增强逻辑的,所以是动态的
Spring AOP 仅支持方法级别的 PointCut;提供了完全的 AOP 支持,它还支持属性级别的 PointCut。
核心成员
- Join Point(连接点)
在系统运行之前将AOP模块织入到OOP模块中的系统执行点,即将在上面进行织入操作的系统执行点。 - Pointcut(切点)
Pointcut代表的是Joinpoint的表达方式,定义了相应的Advice将要发生的地方;将横切逻辑织入当前系统时,需要参照Pointcut规定的Joinpoint的信息,才可以知道应该往哪些Join上织入横切逻辑。 可以直接指定Joinpoint所在的方法名称,还可以用正则表达式来匹配;同时还支持特定专门表达语言 - Advice(增强)
Advice是单一横切关注点逻辑的载体,定义了织入切点中代码具体要做的事,可以分为四种Advice,用来区分发生的具体时间还是执行别的代码
- Before Advice
即在JoinPoint指定位置之前执行的Advice类型,在Before Advice执行完成之后,程序继续执行Joinpoint处继续执行 - After Advice
在相应连接点执行之后执行的Advice类型 - Around Advice
Around对其附加的Joinpoint进行包裹,可以在Joinpoint之前和之后都指定相应的逻辑,甚至于中断或者忽略Joinpoint处原来程序的执行 - Introduction
Introduction可以为原有的对象添加新的特性或行为,属于per-instance的Adv-ice类型,它不会在目标类的所有实例之间共享,而是会为不同的实例对象保存它们各自的状态以及相关的逻辑;其余三种属于per-class类型的,是会在目标对象中间共享的
- Aspect(切面)
Aspect是对系统中的横切逻辑点进行模块化封装的AOP实体概况,可以比作类,其中的包含了Advice和PointCut,而Advice就是Aspect中的方法 - weaving(织入和织入器)
主要功能是将Aspect和其他对象连接起来,并创建织入(Weaving)就是把Aspect横切关注逻辑集成到现存系统中;完成织入过程的工具就叫织入器(Weaver),AspectJ有专门的工具来完成织入操作,即ajc,ajc是Aspect的织入器;JBOSS AOP采用自定义的类加载器来完成织入,自定义的类加载器就是它的织入器;Spring AOP采用ProxyFactory来完成最终的织入操作,所以Spring AOP通用的织入器就是ProxyFactory类 - Target(目标对象)
符合Pointcut所指定的条件,将在织入过程中被织入横切逻辑(即Advice)的对象,就是目标对象。
实现:
采用XML文件配置的方式:
当采用基于配置文件来配置Spring aop时,所有的关于Spring aop的配置都必须写在标签元素中,声明一个切面采用元素来定义,例如:
<aop:config>
<!-- 配置切点 -->
<aop:pointcut expression="execution(* com.aop.run.*.*(..))"
id="runpoint" />
<!-- 配置前置切面 -->
<aop:aspect ref="beforelog">
<aop:before method="beforeRun" pointcut-ref="runpoint" />
</aop:aspect>
<!-- 配置后置切面 -->
<aop:aspect ref="afterlog">
<aop:after method="afterRun" pointcut-ref="runpoint" />
</aop:aspect>
</aop:config>
实例:
基于xml文档配置的实现
目标类:
public class AopDemo {
public AopDemo() {
}
public void Run() {
System.out.println("基本类的方法运行了");
}
}
两个横切逻辑实现类(这里也可以放在同一个类中定义两个不同方法):
public class AfterRun {
public void afterRun() {
System.out.println("后置方法执行了"+new Date());
}
}
public class BeforeRun {
public void beforeRun() {
System.out.println("前置方法执行力了"+new Date().toString());
}
}
在xml文件中配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!-- 将指定类TestDaoImpl配置给Spring,让Spring创建其实例 -->
<bean id="beforelog" class="com.aop.log.BeforeRun" />
<bean id="afterlog" class="com.aop.log.AfterRun" />
<bean id="aopdemo" class="com.aop.run.AopDemo" />
<aop:config>
<!-- 配置切点 -->
<aop:pointcut expression="execution(* com.aop.run.*.*(..))"
id="runpoint" />
<!-- 配置前置切面 -->
<aop:aspect ref="beforelog">
<aop:before method="beforeRun" pointcut-ref="runpoint" />
</aop:aspect>
<!-- 配置后置切面 -->
<aop:aspect ref="afterlog">
<aop:after method="afterRun" pointcut-ref="runpoint" />
</aop:aspect>
</aop:config>
</beans>
测试方法:
public class Test {
public static void main(String[] args) {
ApplicationContext appCon =
new ClassPathXmlApplicationContext("applicationContext.xml"); //classpath:applicationContext.xml
AopDemo ad = (AopDemo)appCon.getBean("aopdemo");
ad.Run();
}
}
结果:
前置方法执行力了Fri Mar 08 08:35:33 CST 2019
基本类的方法运行了
后置方法执行了Fri Mar 08 08:35:33 CST 2019
基于注解的实现:
声明切面的注解是@Aspect,@PointCut用来定义横切逻辑
目标类方法不变,我们定义一个Aspect,定义两个方法,一个前置类型的,一个环绕类型的
/**
*
*/
package com.aop.log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @ClassName: LogAspect.java
* @Description: 切面
* @version: v1.0.0
*/
@Aspect
@Component
public class LogAspect {
@Pointcut("execution( * com.aop.run.*.*(..))")
public void myPointCut() {
}
@Before("myPointCut()")
public void before() {
System.out.println("前置方法执行了");
}
@Around("myPointCut()")
public void time(ProceedingJoinPoint joinPoint) {
long a = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("service方法总共运行了"+(System.currentTimeMillis()-a)+"毫秒");
}
}
目标类上面添加@Component注解,作用相当于“< bean id=“aopdemo” class=“com.aop.run.AopDemo” />”
@Component(value="aopdemo")
public class AopDemo {
public AopDemo() {
}
public void Run() {
System.out.println("基本类的方法运行了");
}
}
然后在xml文件中开启包扫描,并不需要再手动配置方法和bean
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<context:component-scan base-package="com.aop"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 将指定类TestDaoImpl配置给Spring,让Spring创建其实例 -->
<!-- <bean id="beforelog" class="com.aop.log.BeforeRun" />
<bean id="afterlog" class="com.aop.log.AfterRun" />
<bean id="aopdemo" class="com.aop.run.AopDemo" />
<aop:config>
配置切点
<aop:pointcut expression="execution(* com.aop.run.*.*(..))"
id="runpoint" />
配置前置切面
<aop:aspect ref="beforelog">
<aop:before method="beforeRun" pointcut-ref="runpoint" />
</aop:aspect>
配置后置切面
<aop:aspect ref="afterlog">
<aop:after method="afterRun" pointcut-ref="runpoint" />
</aop:aspect>
</aop:config> -->
</beans>
在上面我们可以看到,原来我们配置的都已经注释了,因为我们用注解标明了类和方法,相当于告知编译器,让编译器和虚拟机去帮我们完成配置和注入