1、AOP和AspectJ概述(Spring AOP是什么?AOP有什么用?)
1.1 AOP简介
- 在软件行业,AOP为Aspect Oriented programming 的缩写,意为:面向切面编程,它是一种编程思想。AOP是OOP(面向对象编程)思想的延续。AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码的编写方式(例如性能监视、事务管理、安全检查、缓存、日志记录等)
- AOP的核心思想:基于代理思想,对目标对象创建代理对象,在不修改原目标对象的情况下,通过代理对象,调用增强功能代码,从而对原有业务方法的功能进行增强。
1.2 AOP的作用
- 利用AOP可以对业务逻辑的各个部分进行分离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
- AOP采用横向抽取机制,取代了传统继承体系的纵向机制。
- AOP的经典应用场景:事务管理、性能监视、安全、缓存、日志.....
1.3 Spring AOP编程的两种方式
- Spring AOP 使用纯Java代码实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码。
AspectJ
是一个基于Java的AOP框架,Spring2.0开始,Spring AOP引入AspectJ的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。
1.4 AspectJ是什么?能做什么?
AspectJ是一个易用且功能强大的AOP框架,并不是Spring的一部分。AspectJ的全称是Eclipse AspectJ,其官网地址是:www.eclipse.org/aspectj/. AspectJ的特性如下:
- 是一种基于Java平台的面向切面编程的语言
- 兼容Java,可以无缝扩展
- 易学易用,可以单独使用,也可以整合到其他框架中,单独使用AspectJ需要使用专门的编译器ajc
1.5 AspectJ和Spring AOP的区别?
两者的区别如下: Spring AOP:
- 基于动态代理来实现,默认如果使用接口,那么会用JDK提供动态代理实现,如果是方法则使用CGLib提供代理实现。
- Spring AOP需要依赖IOC容器来管理,并且只能用于Spring容器,使用纯Java代码实现。
- 在性能上,由于Spring AOP是基于动态代理实现的,在容器启动时需要生成代理实例,在方法调用上也会增加桟的深度,使用Spring AOP的性能不如AspectJ好。
AspectJ:
- AspectJ属于静态织入,通过修改代码来实现,一般AOP有三种织入的方式:
- 编译期织入(Compile-time weaving):类A中使用了AspectJ添加了一个属性,类B引用了它,这个场景就需要在编译时进行织入,否则没法编译类B。
- 编译后织入(Post-compile weaving):当目标代码已经编译成.class字节码文件了的时候可以使用此方法来织入增强的代码
- 类加载后织入(Load-time weaving):在类加载的时候进行织入,要实现这个时期的织入一般有两种办法:自定义类加载器专门来进行织入;在JVM启动的时候指定AspectJ提供agent:
-javaagent:xxx/xxx/aspectjweaver.jar
- AspectJ是AOP编程的完全解决方案,Spring AOP则只是致力于解决企业级开发中最普遍的AOP(方法织入)
- 由于AspectJ在实际运行之前就完成了织入,因此由AspectJ生成的类是没有额外的运行时开销的。
2、AOP的一些术语【了解】
AOP(Aspect Oriented Programming)像大多数技术一样形成了自己的术语,而且这些术语比较难理解,不论是否理解都对编程影响不太大,但是最起码要清除有这样一个东西。
-
连接点(Joinpoint) 程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring 仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。
-
切点(Pointcut) 每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP 通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。
-
通知或增强(Advice) 增强是织入到目标类连接点上的一段程序代码,在 Spring 中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。
-
目标对象(Target) 增强逻辑的织入目标类。如果没有 AOP,目标业务类需要自己实现所有逻辑,而在 AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用 AOP 动态织入到特定的连接点上.
-
引介(Introduction) 引介是一种特殊的增强,它为类添加一些属性和方法。这样 ,即使一个业务类原本没有实现某个接口,通过 AOP 的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
-
织入(Weaving) 织入是将增强添加对目标类具体连接点上的过程。AOP 像一台织布机,将目标类、增强或引介通过 AOP 这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP 有三种织入的方式:
-
代理(Proxy) 一个类被 AOP 织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。
-
切面(Aspect) 切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP 就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
对于 Spring 中的 AOP 术语,确实不好理解,为了帮助大家理解这些术语,我们想象一下这样的场景:
有 2 条高速公路可以通向北京,分别为 A,B(相当于 2 个目标个业务 Target(功能),每条高速公路上有 3 个服务站(相当于每个业务上的连接点(Joinpoint)有 3 个),在每条高速公路的第 2 个服务站需要测速(相当于一个切点(Pointcut),匹配了 3 个连接点),在第一条(A)进入服务站之前进行测速,在第二条(b)进入服务站之后测速(通知或增强 Advice,其实也定义了调用测速功能进行测速,以及怎么测),每条高速路第 2 个服务站加入测速功能,这件事情相当于切面 Aspect,它包含了测速功能的定义与切点的定义,将测速功能应用到服务站上,这个过程叫织入(Weaving)
3、AOP的实现原理
接下来是重点,我们来学习一下AOP的实现原理,并利用原理自己手动实现AOP。 AOP底层采用代理机制进行实现,具体的实现方法有以下两种:
1)接口+实现类:采用jdk的动态代理 2)使用实现类:Spring采用了cglib字节码增强
接下来我们就用这两个原理分别自己手动实现AOP。
4、使用jdk的动态代理实现AOP
实现思路:使用jdk中的Proxy类的newProxyInstance方法来获得一个代理对象,此后当使用某个对象的时候就使用这个代理对象而不直接去new对象,好了废话不多说,直接上代码
//Java newProxyInstance方法API
public static Object newProxyInstance(
ClassLoader loader, 类加载器,写当前类的类加载器
Class<?>[] interfaces, 接口,就是你要增强的业务类的接口
InvocationHandler h){} 处理器,可以自己实现InvocationHandler接口的invkoe方法实现一个处理器
步骤: 1. 写一个普通的接口以及这个接口的实现类 2. 写一个切面类(就是一个普通的java类,里面的方法写要增强的功能) 3. 上面的操作完成后,在工厂类(为了方便,当然也可以不写这个工厂类)中首先创建一个目标类对象(就是new一个业务类的对象),接着new一个切面类对象,使用动态代理把切面类中的增强功能织入到目标方法的前后,下面是示例代码:
4.1 首先写定义一个业务类接口:UserServiceBase.java
package com.xzy.sevice;
public interface UserSeviceBase {
/**
* 增加用户
*/
public void addUser();
/**
* 删除用户
* @param id
*/
public void deleteUser(int id);
/**
* 更新用户
* @param id
*/
public void updateUser(int id);
}
4.2 写一个业务接口的实现类:UserSeviceImpi.java
package com.xzy.sevice;
public class UserSeviceImpi implements UserSeviceBase {
@Override
public void addUser() {
System.out.println("增加了1个用户");
}
@Override
public void deleteUser(int id) {
System.out.println("删除了id为"+id+"的用户");
}
@Override
public void updateUser(int id) {
System.out.println("id为"+id+"的用户更新了");
}
}
4.3 基于Java原生API写一个切面类:MyAspect.java
package com.xzy.aspect;
import java.util.Date;
//切面类:就是一段增强功能的代码
public class MyAspect {
public void before(){
System.out.println(".......执行操作前日志......."+new Date());
}
public void after(){
System.out.println(".......执行操作后日志......."+new Date());
}
}
4.4 写一个工厂类,专门用于生产代理对象(可以没有)
package com.xzy.sevice;
import com.xzy.aspect.MyAspect;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ServiceFactory {
/**
* 产生一个 UserSevice
*
* @return
*/
public static UserSeviceBase createUserService() {
//1.创建目标对象
final UserSeviceBase userSevice = new UserSeviceImpl();
//2.声明切面类
final MyAspect aspect = new MyAspect();
//3.使用动态代理来把切面类中的增强方法切入目标方法前后
UserSeviceBase proxyService = (UserSeviceBase) Proxy.newProxyInstance(
ServiceFactory.class.getClassLoader(),
userSevice.getClass().getInterfaces(),
//InvocationHandler是一个接口,它里面只有一个invoke方法,这里也可以在外面单独写一个类实现InvocationHandler接口,然后在这里new这个类的对象也可以
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//目标方法开始前的增强代码
aspect.before();
//通过反射调用目标方法
Object obj = method.invoke(userSevice, args);
//目标方法执行后的增强代码
aspect.after();
return obj;
}
}
);
return proxyService; //返回代理对象
}
}
4.5 测试:
package com.xzy;
import com.xzy.sevice.ServiceFactory;
import com.xzy.sevice.UserSeviceBase;
import org.junit.Test;
/**
* Unit test for simple App.
*/
public class AppTest {
/**
* 使用Java的动态代理机制,手动实现AOP编程
*/
@Test
public void test1(){
//直接从工厂中拿代理对象
UserSeviceBase userSevice= ServiceFactory.createUserService();
userSevice.deleteUser(10);
userSevice.updateUser(3);
userSevice.addUser();
}
}
测试结果:

5、使用Cglib实现
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib 下面是CGLib的组成结构: CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。接下来我们就用CGLib手动实现AOP
5.1 首先,需要导入Cglib所需的jar包,Maven依赖写法如下:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
5.2 实现业务类
还是需要一个业务类(这次不需要接口了)以及一个切面类,前面写过,这里就跳过了。
5.3 基于cglib的API实现切面类
接着在刚才的工厂类中再写一个方法createUserService2()
,步骤还是那几步,只不过这次是用cglib提供的api来写,具体如下:
/*
*使用cglib实现AOP
*
*/
public static UserSeviceBase createUserService2() {
//1.创建目标对象
final UserSeviceImpl userSevice = new UserSeviceImpl();
//2.声明切面类
final MyAspect aspect = new MyAspect();
//3.cglib核心类Enhancer
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(userSevice.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
aspect.before();
/*
*proxy代理是目标类的子类
*/
Object obj = methodProxy.invoke(userSevice, args);
aspect.after();
return obj;
}
});
UserSeviceImpl proxy = (UserSeviceImpl) enhancer.create();
return proxy; //返回代理对象
}
5.4 测试:
package com.xzy;
import com.xzy.sevice.ServiceFactory;
import com.xzy.sevice.UserSeviceBase;
import org.junit.Test;
/**
* Unit test for simple App.
*/
public class AppTest
{
/**
* 使用cglib手动实现AOP编程
*/
@Test
public void test2(){
UserSeviceBase userSevice= ServiceFactory.createUserService2();
userSevice.deleteUser(10);
userSevice.updateUser(3);
userSevice.addUser();
}
}
测试结果:

可以看到结果和刚才用JDK动态代理的结果一样,但是这里要特别注意:
- jdk代理只能动态代理接口+实现类的形式;
- Cglib代理的优势是可以直接代理普通的类,但同时接口也可以