我们知道,使用面向对象编程(OOP)有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全检测等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量重复代码,所以就有了一个对面向对象编程的补充:面向切面编程(AOP),AOP所关注的方向是横面的,不同于OOP的纵面。
Spring AOP的实现原理是 动态代理,实现方式有两种: JDK动态代理和CgLib动态代理。
JDK动态代理
Jdk动态代理是基于 InvocationHandler 、 Proxy 来实现,由Java内部的 反射 机制来实例化代理对象,并调用委托类方法。
一个JDK实现动态代理的Demo:
- 目标接口
public interface TestAop {
void process();
}
- 目标实现类
@Component
public class TestAopImpl implements TestAop {
@Override
public void process() {
System.out.println("testAop.process()");
}
}
- 切面类
@Component
public class AopAspect {
public void before() {
System.out.println("前置通知");
}
public void after() {
System.out.println("后置通知");
}
}
- 测试类
public class MainTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
final TestAop testAop = ac.getBean(TestAop.class);
final AopAspect aopAspect = ac.getBean(AopAspect.class);
//创建代理
TestAop testAopProxy = (TestAop) Proxy.newProxyInstance(
ac.getClassLoader(),
testAop.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前执行
aopAspect.before();
//执行目标类方法
Object obj = method.invoke(testAop, args);
//后执行
aopAspect.after();
return obj;
}
}
);
testAopProxy.process();
}
}
输出结果:
前置通知
testAop.process()
后置通知
CgLib动态代理
基于CGlib 动态代理模式是基于 继承 被代理类生成代理子类,不用实现接口。只需要被代理类是非final 类即可,cglib动态代理底层是借助asm字节码技术。
一个CgLib实现动态代理的Demo:
- 目标类、切面类都同上
- 测试类
public class MainTest {
public static void main(final String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
final AopAspect aopAspect = ac.getBean(AopAspect.class);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestAopImpl.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
aopAspect.before();
Object result = methodProxy.invokeSuper(o, objects);
aopAspect.after();
return result;
}
});
TestAopImpl testAop = (TestAopImpl) enhancer.create();
testAop.process();
}
}
输出结果:
前置通知
testAop.process()
后置通知
Spring AOP
与上述两种编码方式对比:
- Spring AOP代理是 基于IOC容器 负责生成与管理:切面Bean的创建、通知的应用类、应用时间点等关系也都由IOC容器管理;
- Spring根据策略判断使用JDK和还是CGLIB实现动态代理,策略规则为:
- Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理。
- CGLib代理其生成的动态代理对象是目标类的子类,也可以强制使用CGLib模式。
Spring中AOP相关概念
- 通知(advice):在方法执行之前或之后采取的实际操作。 这是在Spring AOP框架的程序执行期间调用的实际代码片段
- 连接点(JointPoint):应用程序中使用Spring AOP框架采取操作的实际位置
- 切点(PointCut):在切点应该执行Advice
- 切面(Aspect)
- 引入(Introduction):引用允许我们向现有的类添加新的方法或者属性
- 织入(Weaving):创建一个被增强对象的过程
Spring中提供了3种类型的AOP支持:
-
基于代理的经典Spring AOP,低版本的spring配置方式;
-
使用XML配置方式,纯POJO切面,aop命名空间
-
@AspectJ注解方式:与xml形式的效果相同,是最简洁和最方便的;
有关这三种方式的使用方式及详细介绍:Spring AOP 使用介绍,从前世到今生
我们重点分析基于注解方式的Spring AOP实现。首先是一个Demo:
- 配置文件:
<context:component-scan base-package="example.aop"/>
<aop:aspectj-autoproxy/>
- 目标接口
public interface TestAop {
void process();
}
- 目标实现类
@Component
public class TestAopImpl implements TestAop {
@Override
public void process() {
System.out.println("testAop.process()");
}
}
- 切面类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AopAspect {
//定义切点
@Pointcut("execution(* example.aop.*.*(..))")
public void aopPointcut() {
}
@Before("aopPointcut()")
public void before() {
System.out.println("前置通知");
}
//后置通知
@After("aopPointcut()")
public void after() {
System.out.println("后置通知");
}
//环绕通知
@Around("aopPointcut()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知:环绕前");
pjp.proceed();//执行方法
System.out.println("环绕通知:环绕后");
}
}
- 测试类
public class MainTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
TestAop testAop = ac.getBean(TestAop.class);
testAop.process();
}
}
输出结果:
环绕通知:环绕前
前置通知
testAop.process()
环绕通知:环绕后
后置通知