赞
踩
这篇博客主要记载的是我对Aop的理解,和以前使用Aop的一种模糊理解。Aop底层是动态代理,动态代理的底层又是反射。主要是针对Spring-Aop的理解。需要具备Aop的切入点、切面、链接点、通知等基础知识。
反射是Java自身核心的一个知识点,反射具体就是指在程序运行期间,对于任意一个类可以获取这个类的属性和方法,对于任意一个对象,能够调用它的任意一个方法和属性。
简单的代码演示:参考:[简单的反射](https://www.cnblogs.com/haodawang/p/5967185.html),主要是使用Java类库中reflect包中的各种方法。
import java.lang.reflect.*; public class Main { public static void main(String[] args) throws Exception{ //返回A的构造方法 Constructor c = A.class.getConstructor(); //返回A类的所有为public 声明的构造方法 Constructor[] cons = A.class.getConstructors(); //返回A类所有的构造方法,包括private Constructor[] cons2 = A.class.getDeclaredConstructors(); //返回A类的第一个public 方法 Method m = A.class.getMethod("say"); //执行 m.invoke(A.class.newInstance(), null); //返回A类所有的public 方法 Method[] ms = A.class.getMethods(); //返回A类所有的方法,包括private Method[] allMs = A.class.getDeclaredMethods(); //返回A类的public字段 Field field = A.class.getField("i"); System.out.println(field.get(A.class.newInstance())); //返回A类的static 字段 System.out.println(field.get(null)); } } class A{ public int i = 1; public static int b = 2; public A(){ System.out.println("无参构造"); } private A(String s){ System.out.println("有参构造"+s); } public void say(){ System.out.println("say"); } }
在了解动态代理之间,我们需要了解静态代理。静态代理是指实现同一个接口的两个类。一个类是代理类,一个类是被代理类。在代理类可以new一个被代理类,达到代理的效果。就比如房东、中介、和客户的关系。房东和中介都有卖房方法,房东是被代理类,中介是代理类。房东只要提自己的卖房要求,至于签合同、带领客户参加这种活动可以交给中介进行。中介就是代理类。静态代理是属于编译期间,也就是编译期间就有真正的class文件,缺点是它只能针对特定的类。而动态代理却不同,它可以在运行期间,具有普适性。Aop就是用的动态代理。
动态代理是怎么实现的?这就是反射,动态代理依靠的就是反射。在Java种有两种动态代理,1、jdk动态代理(接口)2、cglib动态代理(父类)Spring中默认是jdk动态代理。
在jdk动态代理种,要求代理类和被代理类必须实现共同接口。创建代理对象的步骤:1、实现InvocationHandler接口 2、使用newProxyInstance方法
//创建一个动物接口,接口中就只有一个方法
public interface Animal {
void eat();
}
===================================================================================
//创建一个猫类实现这个接口,这个小猫作为被代理类
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("吃鱼");
}
}
然后书写一个类,这个类主要目的是创建代理类(猫的代理对象),并且执行增强的方法
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyJdk implements InvocationHandler { private Object object; public Object createProxyCat(Object o){ this.object=o; /** * newProxyInstance()方法中有三个参数 * 第一个参数:loader 是代理类的类加载器 * 第二个参数:interfaces 代理类实现的接口列表 * 第三个参数:h,也就是实现InvocationHandler的类,用于将方法分派到的调用程序 */ return Proxy.newProxyInstance( object.getClass().getClassLoader(), object.getClass().getInterfaces(), this); } /** * * @param proxy 调用方法的代理实例 * @param method 代理对象上的接口方法 * @param args 参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("闻到香味,跑过来!"); //实现被代理类的方法 Object invoke = method.invoke(object, args); System.out.println("吃完离开"); return invoke; } //进行测试 public static void main(String[] args) { ProxyJdk jdk=new ProxyJdk(); Animal proxyCat = (Animal)jdk.createProxyCat(new Cat()); proxyCat.eat(); } }
#运行结果
闻到香味,跑过来!
吃鱼
吃完离开
cglib 动态代理的思想是,一个类继承父类的所有公开方法就可以重写这些方法然后进行增强。创建代理对象,需要实现MethodInterceptor接口,并使用Enhancer类。
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class ProxyCglib implements MethodInterceptor { private Enhancer enhancer=new Enhancer(); public Object createProxy(Class clazz){ //设置为公共的类 enhancer.setSuperclass(clazz); //建立关联关系 enhancer.setCallback(this); return enhancer.create(); } /** * * @param o 代理对象本身 * @param method 被代理的方法 * @param objects 函数调用的参数 * @param methodProxy 方法的代理 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("问起来鱼不行"); //相当与jdk代理中的invoke方法() Object o1 = methodProxy.invokeSuper(o, objects); System.out.println("受不了,跑步离开"); return o1; } public static void main(String[] args) { ProxyCglib proxyCglib=new ProxyCglib(); //创建代理对象 //这里传的参数主要是类的,可以是Class.forName("类的全名"),或者是 new Cat().getClass Animal proxyCat =(Animal) proxyCglib.createProxy(Cat.class); proxyCat.eat(); } }
#结果
问起来鱼不行
吃鱼
受不了,跑步离开
在SpringBoot中Aop的使用非常简单,SpringBoot Aop帮我们封装了很多底层,默认使用的是jdk动态代理,可一配置打开cglib动态代理。
可以选择 aspectjweaver 也可以选择Aop starter依赖,建议使用starter依赖,因为starter一个包就包含了很多依赖项,并且容易配置。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.4</version>
</dependency>
切入点的匹配方式使用的是execution进行匹配。
@Component @Aspect public class TestAop { //pointcut 切入点的集合,即在哪里干的集合 @Pointcut("execution(* com.andrew.study.service.impl.TestServiceImpl.*(..))") private void pointCutMethod(){ } //环绕通知 @Around("pointCutMethod()") public void doAround(ProceedingJoinPoint pip){ System.out.println("----------"); System.out.println("1200一个月,可以不"); System.out.println("不行,起码1800一个月"); try { Object proceed = pip.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("成交"); } //前置通知 @Before("pointCutMethod()") public void doBefore(){ System.out.println("请问是陈先生吗?"); } @After("pointCutMethod()") public void doAfter(){ System.out.println("入住之后,不准改变房子的格局"); } }
在切入点的匹配上选择自定义注解的方式
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Test2 {
}
修改后的切面类为
@Component @Aspect public class Test2Aop { @Pointcut("@annotation(com.andrew.study.aspect.Test2)") private void test(){ } @Before("test()") public void doBefore(){ System.out.println("今天有什么菜呢?"); } @Around("test()") public void doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("开饭了"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("小明洗碗!"); } @After("test()") public void doAfter(){ System.out.println("谁最后一个吃完谁洗碗"); } }
在自定义注解方式下,需要在需要增强的方法上加上注解。
@Service
public class Teste2ServiceImpl implements ITestService {
@Test2
@Override
public void test1() {
System.out.println("有番茄炒蛋、水煮肉片");
}
}
# 结果
开饭了
今天有什么菜呢?
有番茄炒蛋、水煮肉片
谁最后一个吃完谁洗碗
小明洗碗!
对于没有自定义注解的方式切面类,直接使用就行
@RequestMapping("/test1")
public void test(){
testService.test1();
}
# 结果
----------
1200一个月,可以不
不行,起码1800一个月
请问是陈先生吗?
这个房子卖给你了
入住之后,不准改变房子的格局
成交
前面几点,是Aop中比较简单的实现原理,下面介绍Aop在SpringBoot中的工作流程。
在了解Aop的底层原理之前,我们需要了解BeanFactoryPostProcessor 和 BeanPostProcess ,这两个接口都是spring 通过配置文件或xml获取Bean声明生成后的BeanDefinition后允许对生成BeanDefinition进行再次封装的入口。在Spring或者Spring Boot进行校验后,应用程序会根据@Aspect注解切面类进行解析,它会根据PointCut匹配代理类,然后创建对象。核心校验方法是wrapIfNecessary,这个方法中有一个getAdvicesAndAdvisorsForBean,主要是返回一个Advisor数据集合,这个主要是将bean和集合中Advisor的Advice的pointCut匹配到。如果匹配到,就为这个bean创建对象。
具体流程
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。