定义
AOP (Aspect-Oriented Programming) : 面向切面编程,利用 AOP 可以对业务的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可用性,同时提高了效率;通俗来讲就是在不修改源代码的情况下,对源代码的功能进行增强。
面向切面编程(Aspect-oriented Programming 简称AOP)是
面向对象编程(Object-oriented Programming 简称OOP)的一种功能补充。
Aop 的一些术语。
Join Point(连接点) | 连接点,这些连接点可以是方法或构造函数的调用和执行、异常的处理、字段分配和访问等。我们通常将类中的多个方法看做连接点。 |
---|---|
Pointcuts(切入点) | 切入点,通常是一个表达式,是我们需要增强的地方。例如对象中有十个方法,其中add()方法被增强了,那么这个方法就是切入点。 |
Advice(增强/通知) | 实际增强的部分,比如记录日志、判断权限、事务管理等。增强有多个类型:前置增强、后置增强、环绕增强、异常增强、最终增强。在切入点执行增强。 |
Aspects(切面) | 由切点和增强组成,它的定义非常像一个类,除了横切成员之外,还可以有方法、字段和初始化器。那么将增强应用到切入点上的过程就是在处理切面。 |
Aspectj
说动面相切面编程,那么就得说一下这个 AOP 框架 Aspectj !
Aspectj一个易用的、功能强大的 AOP 编程语言。它是一个独立的框架。
官网地址是:The AspectJ Project | The Eclipse Foundation 。
文档:AspectJ Documentation | The Eclipse Foundation
Spring AOP
再说一下 Spring AOP !
Spring AOP 使用了AspectJ 的 Annotation。使用 Aspect 来定义切面,使用 Pointcut 来定义切入点,使用 Advice 来定义增强处理。虽然使用了 Aspect 的 Annotation,但是并没有使用它的编译器和织入器。其实现原理是JDK 动态代理,在运行时生成代理类。
Spring 对 @AspectJ 方面配置的支持,需要在配置文件中添加如下配置:
<aop:aspectj-autoproxy/>
实现原理:
如果我们配置了AOP,那么 Spring 在创建相关的 bean 的时候,会给这个 bean 一个代理对象,我们后续对 bean 中方法的调用,实际上调用的是代理类重写的代理方法。
Spring的 AOP 使用了两种动态代理,分别是 JDK 的动态代理以及 CGLib 的动态代理:
- 被代理的类有接口 , 使用 JDK 的动态代理,创建接口实现类代理对象,增强类的方法
- 被代理的类没有接口,使用 CGLIB 代理,创建子类的代理对象,增强类的方法
:::tips 在J AVA平台中,对于 AOP 增强,有三种方式:
- 编译期:编译器把切面的调用编译进字节码,这种方式需要扩展编译器;
- 类加载器:目标被加载到 JVM,通过特殊的加载器,对目标字节码进行增强;
- 运行期:目标对象和切面都是 JAVA 对象,通过 JVM 的动态代理或者第三方库运行期动态进行增强。(Spring 使用的这个) :::
代码测试实现:spring - aop
Spring AOP 和 AspectJ 的区别 ?
Spring AOP 和 AspectJ 都是实现 AOP 的框架,其中 Spring AOP 是基于动态代理实现,而 AspectJ 是基于字节码实现的 AOP 框架。
下面是Spring AOP 和 AspectJ 的一些区别:
性能 | Spring AOP 基于代理实现 AOP,相比之下,AspectJ 基于字节码操作实现 AOP,因此 AspectJ 比 Spring AOP 更快,但是Spring AOP 的性能也很不错。 |
---|---|
表达式 | Spring AOP 仅支持使用 AspectJ 风格的切点表达式,而 AspectJ 使用的是一种更强大的 AspectJ 切点定义语言,并且提供了更多的控制和灵活性。 |
编程模型 | Spring AOP 使用声明式编程模型,因为它允许您使用注解或 XML 配置来定义切面和切点。使用 AspectJ 时,您必须在 Java 类中显式地定义切面和切点。 |
可移植性 | Spring AOP 是 Spring 框架的一部分,因此它具有更好的可移植性。而 AspectJ 则需要独立使用,相对来说不太便于集成到其他框架中。 |
综上 AspectJ 的灵活性和性能优势使它更适合复杂的 AOP 应用程序。
对于简单的 AOP 场景,Spring AOP 是一种更简单和更易于使用的框架。
JDK 代理
静态代理
准备测试目标接口
public interface UserService {
void save(int a);
void get();
void delete(int id);
}
@Service("userService")
public class UserServiceImpl implements UserService {
public void save(int a) {
System.out.println("save 方法执行");
}
public void get() {
System.out.println("get 方法执行");
}
public void delete(int id) {
System.out.println("delete 方法执行");
}
}
静态代理,纯手动
@Component
public class UserServiceProxy implements UserService {
private final UserServiceImpl userService = new UserServiceImpl();
@Override
public void save(int a) {
System.out.println("proxy save method invoke");
userService.save(a);
}
@Override
public void get() {
}
@Override
public void delete(int id) {
}
}
测试
@DisplayName("JDK静态代理")
@Test
public void jdkStaticProxy() {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext("cn.lichenghao.aop");
UserServiceProxy userServiceProxy = context.getBean(UserServiceProxy.class);
userServiceProxy.save(1);
}
优点简单;缺点也很明显需要给每个类手动设置代理,如果目标对象很多的话…..
动态代理
需要实现InvocationHandler来实现对方法的代理。
/**
* JDK动态代理
*
* @author lichlaughing
*/
@Component
public class JDKServiceDynamicProxy implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
/**
* @param proxy 代理对象
* @param method 方法
* @param args 参数
* @return Object
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException {
System.out.println("UserServiceDynamicProxy before do");
Object result = method.invoke(target, args);
System.out.println("UserServiceDynamicProxy after do");
return result;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader()
, target.getClass().getInterfaces(), this);
}
}
动态代理有唯一的工具类。Proxy来生成代理对象包括三个参数:
- 第一个参数,被代理对象的类加载器;
- 第二个参数,被代理对象实现的所有接口数组;
- 第三个参数InvocationHandler的实现类;
测试
@DisplayName("JDK动态代理")
@Test
public void jdkDynamicProxy() {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext("cn.lichenghao.aop");
// 目标对象
UserService userService = (UserService) context.getBean("userService");
// 代理对象
JDKServiceDynamicProxy dynamicProxy
= context.getBean(JDKServiceDynamicProxy.class);
dynamicProxy.setTarget(userService);
UserService userServiceProxy = (UserService) dynamicProxy.getProxyInstance();
// 执行测试方法
userServiceProxy.save(1);
}
优点
JDK动态代理是JDK原生的,不需要任何依赖即可使用;
通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;
缺点
如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;
JDK动态代理无法为没有在接口中定义的方法实现代理;
JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;
CGLIB 代理
CGLIB(Code Generation Library),是一个强大的,高性能,高质量的字节码生成库。相比于JDK动态代理,完全不受代理类必须实现接口的限制,而且CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。
同样需要准备代理方法类。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib proxy before do");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("cglib proxy after do");
return result;
}
}
测试代理
@DisplayName("CGLIB动态代理")
@Test
public void cglibDynamicProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new MyMethodInterceptor());
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.save(1);
}
或者简化成工具
public class CglibDynamicProxy implements MethodInterceptor {
private Class<?> parentClass;
public void setParentClass(Class<?> parentClass) {
this.parentClass = parentClass;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib proxy before do");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("cglib proxy after do");
return result;
}
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(parentClass);
enhancer.setCallback(this);
return enhancer.create();
}
}
测试下
@DisplayName("CGLIB动态代理")
@Test
public void cglibDynamicProxy2() {
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy();
cglibDynamicProxy.setParentClass(UserServiceImpl.class);
UserService proxyInstance = (UserService) cglibDynamicProxy.getProxyInstance();
proxyInstance.save(1);
}