SpringBoot 对于AOP的理解
我们都知道OOP(面向对象编程),它允许开发者定义纵向的关系,可以将具体的事务抽取成一个类,并在类的内部定义需要的属性和方法,但是OOP不能定义横向的关系,便造成了大量代码的重复,而不利于各个模块之间的重用性。此时便诞生了我们的AOP(面向切面编程),它作为面向对象的一种补充,可以将那些与业务无关,却对多个对象产生影响的共同行为和逻辑,抽取并封装成一个可重用的模块,这个模块就称为切面(Aspect),从而减少系统中重复的代码,并降低了代码之间的耦合度,同时提高了系统之间的可维护性,现在AOP主要用于权限认证,日志处理,事务处理等方面。
在Spring中,AOP的动态代理的方式主要有两种:
- JDK动态代理
- CGLIB动态代理
其中我们分别简单介绍一下JDK动态代理和CGLIB动态代理
- JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
- 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
JDK动态代理的简单实现
条件:
- (1) 进行动态代理的类需要存在接口
- (2) 利用java.lang.reflect.Proxy类的newProInstance()方法,以及InvocationHandler实现动态代理
我们首先定义一个接口:
CustomerDao:
public interface CustomerDao {
void add();
void update();
void delete();
}
然后我们创建一个上面接口的实现类:
CustomerDaoImpl:
public class CustomerDaoImpl implements CustomerDao{
@Override
public void add() {
System.out.println("CustomerDaoImpl add() running...");
}
@Override
public void update() {
System.out.println("CustomerDaoImpl update() running...");
}
@Override
public void delete() {
System.out.println("CustomerDaoImpl delete() running...");
}
}
紧接着,我们此时创建一个代理对象【proxyInstance】,然后使用该代理对象完成目标类CustomerDaoImpl的业务逻辑
public class CustomerProxy {
public static void main(String[] args) {
//创建我们的CustomerDao接口的实现类
CustomerDaoImpl customerDao = new CustomerDaoImpl();
//使用java.lang.reflect.Proxy的newProInstance()方法创建一个CustomerDaoImpl的实例customerDao的一个代理类
CustomerDao proxyInstance = (CustomerDao)Proxy.newProxyInstance(
//需要传入三个参数:分别为目标类的类加载器,目标类的接口,增强的业务(创建一个InvocationHandler接口匿名内部类完成)
customerDao.getClass().getClassLoader(),
customerDao.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//invoke()方法的参数:proxy【代理固定值】,method【需要增强的方法,切入点】,args【方法运行时的参数】
//我们可以自定义获取需要增强的方法,进行方法的增强操作
if ("add".equals(method.getName())) {
System.out.println("add() before running");
method.invoke(customerDao, args);
System.out.println("add() after running");
} else {
method.invoke(customerDao, args);
}
return null;
}
}
);
//此时proxyInstance即为customerDao【目标类】的一个代理对象
proxyInstance.add();
proxyInstance.update();
proxyInstance.delete();
}
}
输出结果:
add() before running
CustomerDaoImpl add() running...
add() after running
CustomerDaoImpl update() running...
CustomerDaoImpl delete() running...
这样我们就简单地实现了利用JDK方式完成动态代理!
CGLIB动态代理的简单实现
条件:
- 需要导包 : import org.springframework.cglib.proxy.Enhancer;
创建一个目标类:
Person:
public class Person {
public void run() {
System.out.println("Person run() running...");
}
}
然后我们使用CGLIB创建一个该目标类的代理类:
public class PersonCGLIBProxy {
public static void main(String[] args) {
Person person = new Person();
//利用CGLIB动态代理方式
Person personProxy = (Person)Enhancer.create(
// 参数一: 目标类的字节码文件类型 因为用于继承
// 参数二: 增强的业务逻辑
Person.class,
new MethodInterceptor() {
@Override
// 参数一: 代理对象的类型 固定值
// 参数二: 目标类要增强的方法
// 参数三: 方法运行时期需要的参数
// 参数四: 代理方法 忽略
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//拦截执行名称方法
if("run".equals(method.getName())){
System.out.println("run方法之前的功能加强...");
method.invoke(person,args);
System.out.println("run方法之后的功能加强...");
}else{
method.invoke(person,args);
}
return null;
}
}
);
personProxy.run();
}
}
输出结果:
run方法之前的功能加强...
Person run() running...
run方法之后的功能加强...
AOP的常见概念
术语名称 | 定义 |
---|---|
target(目标对象) | 即要增强的队象 |
Proxy(代理对象) | 增强以后的对象 |
JoinPoint(连接点) | 可以被增强的方法 |
PointCut(切入点) | 要被增强的方法 |
Weaving(织入) | 切入点集成到切面形成代理类的过程 |
Aspect(切面) | 切入点+通知/增强 = 切面(即里面有要被增强的方法,以及增强方法具体的增强代码程序) |
Advice(通知/增强) | 做了增强的那段程序代码 |
AOP通知的类型
在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。
Spring切面可以应用5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning ):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
手写AOP : 基于AspectJ实现
第一步 : 在AppConfig类上配置@EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy //开启基于Aspect实现AOP
@ComponentScan("com.feng")
public class AppConfig {
}
第二步 : 定义配置类
@Component
@Aspect
public class UserAspect {
}
注意 : 因为Aspect不是Spring内部的包,需要我们自己引入jar包,这里我们使用gradle构建的Spring源码项目,我们在配置文件中引入AspectJ
build.gradle文件 :
plugins {
id 'java'
}
group 'org.springframework'
version '5.0.18.BUILD-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile project(':spring-context')
compile(project(":spring-instrument"))
testCompile group: 'junit', name: 'junit', version: '4.12'
//引入AspectJ : https://mvnrespository.com/artifact.aspectj/aspectjweaver
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.0'
}
第三步 : 定义一个切入点PointCut,以及通知Advice
@Component
@Aspect
public class UserAspect {
@Pointcut("execution(* com.feng.*.*.*(..))")
public void anyPublicMethod(){
}
@Before("anyPublicMethod()")
public void before() {
System.out.println("UserAspect.before");
}
@After("anyPublicMethod()")
public void after() {
System.out.println("UserAspect.after");
}
}
第四步 : 在com.feng包下定义一个User类 :
@Component
public class User {
public void run() {
System.out.println("User run()方法...");
}
}
测试类 :
public class TestAppConfig {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ac.getBean(User.class);
user.run();
}
}
测试结果 :
UserAspect.before
User run()方法...
UserAspect.after
项目中AOP思想的使用
-
权限管理的拦截器
-
日志处理
-
等等