Spring的IoC和AOP不仅仅是我们学习Spring平台下各个框架的核心基础,同时也是我们出去面试问道的频率最高的面试题了,同时也是大家很难彻底掌握好的技术的,本文就透过本质来给大家来介绍下Spring的AOP,Spring的IoC也会在后续的文章中给大家介绍,欢迎大家一键三连哦!!!
一、代理模式
要讲解清楚Spring的AOP那么我们不得不先来聊下代理模式。
1.代理模式的作用
代理模式的作用是用来增强目标对象的。
上面那么介绍大家可能会感觉比较迷惑,为什么能增强目标对象?为什么要增强目标对象呢?我们举个简单的例子,比如你家拆迁分到了很多的money,这时你想要改变下生活品质这时你会想在吃晚饭的时候找个明星来给你唱歌,这时你需要自己去联系这个明星,然后还有很多的琐事需要处理,如下的结构
这时你会感觉很麻烦,而且这个明星除了唱歌这件事情外还需要处理很多的非核心业务之外的事情,这时他可以请一个经纪人来帮他解决唱歌之外的其他杂事。如下:
那么这里的经纪人其实就相当于代理模式中的代理对象,让我们的目标对象专注于核心功能,其他的非核心业务就由代理对象来完成了。
2.代理模式的实现
通过上面的介绍相信大家应该清楚了代理对象的作用了,那么怎么实现代理对象呢?
这个请大家移步本人的另一篇文章:
https://cloud.tencent.com/developer/article/1409394
专门介绍了代理模式的三种实现方式(静态代理,JDK代理和CGLIB代理)
二、SpringAOP
接下来我们看下SpringAOP的实现原理
1.AOP案例
我们通过日志的案例来给大家来介绍,先看下我们不使用AOP的情况下来实现日志记录方法执行的时间。
1.1 非AOP实现
首先创建一个SpringBoot项目,然后创建IUserService接口,定义如下:
代码语言:javascript复制public interface IUserService {
public void log1();
public void log2();
}
然后创建接口的实现,如下:
代码语言:javascript复制@Service
public class UserServiceImpl implements IUserService {
@Override
public void log1() {
long start = System.currentTimeMillis();
try {
Thread.sleep(5);
System.out.println("log1 方法执行了 ...");
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("log1方法执行耗时:" (end - start));
}
@Override
public void log2() {
try {
Thread.sleep(5);
System.out.println("log2 方法执行了 ...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试代码
代码语言:javascript复制//@SpringBootApplication
@Configuration
@ComponentScan
public class SpringAopDemo02Application {
public static void main(String[] args) {
//SpringApplication.run(SpringAopDemo02Application.class, args);
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringAopDemo02Application.class);
IUserService bean = ac.getBean(IUserService.class);
bean.log1();
}
}
到这儿我们可以看出在Service中我们的核心业务代码和日志模块的代理耦合在了一块,这显然是不合适的,这时AOP就派上用场了。
1.2 AOP实现
Spring中的AOP的实现有多种方式,本文就不具体的来一一介绍了,感兴趣的可以参考本人的另一篇文章
https://blog.csdn.net/qq_38526573/article/details/86441916
本文重点介绍原理,我们需要先添加AspectJ的依赖
代码语言:javascript复制<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
然后定义切面类
代码语言:javascript复制@Aspect
@Component
public class MyAspect {
/**
* 定义一个环绕通知
* @param pjp
* @return
*/
@Around("execution(* com.bobo.service.impl.*.log2(..))")
public Object around(ProceedingJoinPoint pjp){
long start = System.currentTimeMillis();
Object proceed = null;
try {
// 执行目标对象的方法
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(pjp.getSignature().getName() " 执行耗时:" (end - start));
return proceed;
}
}
然后我们在需要添加@EnableAspectJAutoProxy
注解来放开代理的使用
//@SpringBootApplication
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class SpringAopDemo02Application {
public static void main(String[] args) {
//SpringApplication.run(SpringAopDemo02Application.class, args);
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringAopDemo02Application.class);
IUserService bean = ac.getBean(IUserService.class);
bean.log1();
System.out.println("-------------------");
bean.log2();
}
}
通过输入我们发现代理生效了
通过上面的操作你会发现SpringAOP的实现还是比较简单的,也实现了业务代码和系统功能的分离。更利于系统的扩展。
2.AOP原理分析
上面的案例实现了AOP,接下来我们需要分析下AOP的原理,前面介绍了代理模式,我们知道代理模式的实现方式有多种,首先我们来看看AOP是采用的JDK代理还是CGLIB代理呢?
2.1 AOP的本质
其实在Spring的AOP中既有JDK代理的实现也有CGLIB的使用,为什么这么说呢?我们通过演示带大家看看。首先在前面的测试案例的基础上我们通过debug模式来看
通过断点我们发现IUserService的bean对象是一个JDK动态代理的对象。那CGLIB代理呢?我们这样来做。定义一个PersonServiceImpl这个Service没有实现任何的接口
代码语言:javascript复制@Service
public class PersonServiceImpl {
public void show(){
System.out.println("Hello ...");
}
}
然后我们同样的来获取
代码语言:javascript复制 public static void main(String[] args) {
//SpringApplication.run(SpringAopDemo02Application.class, args);
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringAopDemo02Application.class);
IUserService bean = ac.getBean(IUserService.class);
bean.log1();
System.out.println("-------------------");
bean.log2();
System.out.println("********");
PersonServiceImpl personService = ac.getBean(PersonServiceImpl.class);
personService.show();
}
ac.getBean(PersonServiceImpl.class);
这么去写是会报错的
提示是没有找到。这儿大家可以思考下为什么没有获取到? 思考三秒
原因是因为PersonServiceImpl没有实现任何的接口,那么肯定不能使用JDK动态代理,只能使用CGLIB代理,而CGLIB的代理对象是和目标对象没关系的。所以肯定获取到不到,那么这时怎么办呢?
换种思路我们通过BeanName来获取即可。
注意:切面中的 切入点表达式要修改
到这儿其实我们可以发现,在Spring的AOP中如果目标对象实现了接口则使用JDK代理如果目标对象没有实现接口就只能通过CGLIB代理来实现了。
当然你也可以显示的指定就使用CGLIB代理。如下
2.2 源码验证
具体的源码验证请参考本文的下篇文章
为帮助开发者们提升面试技能、有机会入职BATJ等大厂公司,特别制作了这个专辑——这一次整体放出。
大致内容包括了: Java 集合、JVM、多线程、并发编程、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat等大厂面试题等、等技术栈!
大家如果想需要获取以下这些面试题答案以及Java架构学习资料的话请微信扫描下图作者助手的微信:( yuanlaoshi2001 )添加即可免费获取到哦