大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨🌾!
||To Up||
未来村村长正推出一系列【To Up】文章,该系列文章重要是对Java开发知识体系的梳理,关注底层原理和知识重点。”天下苦八股文久矣?吾甚哀,若学而作苦,此门无缘,望去之。“该系列与八股文不同,重点在于对知识体系的构建和原理的探究。
文章目录
一、概念性准备
1、Spring相关概念
(1)Spring
定义:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
目标:Spring为开发者提供一站式的轻量级应用开发平台(框架),抽象了应用开发中的共性问题。
(2)依赖、依赖注入、控制反转
-
依赖:A类中实例化了B类的对象,调用了其方法或属性,我们称A依赖B(类A示例化了B的对象)
-
依赖注入(DI):会将所依赖的关系自动交给目标对象,而不是让对象自己去获取依赖。依赖注入(DI)是控制反转(IoC)的一种方式。(类A不用实例化B的对象,交给第三方对象完成)
-
控制反转(IoC):IoC容器控制对象的创建和外部资源的获取,容器帮助我们查找以及注入对象,即解耦
-
AOP:Aspect Oriented Programming的缩写,意为:面向切面编程。通过预编译方 式和运行期动态代理实现程序功能的统一维护的一种技术。
(3)POJO、Bean、javaBean
- Bean:在 Spring 中,构成应用程序主干并由SpringIOC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化、组装和管理的对象。
- POJO:(Plain Old Java Object)从字面上理解“简单老式的java对象”或者“普通java类”。POJO是一个简单的、普通java对象,特点是有private的属性和public的getter、setter,除此之外不具有任何特殊角色,不继承或不实现任何其它Java框架的类或接口。
- JavaBean是一种JAVA语言写成的可重用组件。JavaBean符合一定规范编写的Java类,不是一种技术,而是一种规范。特点如下:
- 1.所有属性为private。
2.这个类必须具有一个公共的(public)无参构造函数。
3.private属性必须提供public的getter和setter来给外部访问,并且方法的命名也必须遵循一定的命名规范。
4.这个类应是可序列化的,要实现serializable接口。
- 1.所有属性为private。
二、IoC
1、IoC探究开始:简单的IoC应用实现
要想有顺序地,自上而下地探究源码,就得从一个实现了IOC的应用程序出发。由于java面向对象的特性,我们可以从程序中的代码逐步探究到SpringIOC模块实现的最底层源码,所以我们先来看看一个基础的IOC应用如何实现。
(1)导入依赖
这里我们导入520版本的spring-mvc,这个依赖包含了很多要用到的jar。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
</dependencies>
(2)编写一个类
这个类要遵循Spring的规范,才能被IOC容器控制反转:为接受设值注入的属性提供setter方法,sertter方法的命名为set+属性名。
public class HelloSpring {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
@Override
public String toString() {
return "HelloSpring{" +
"property='" + property + '\'' +
'}';
}
}
(3)beans.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="hello" class="com.plord.pojo.HelloSpring">
<property name="property" value="Hello,Spring!"/>
</bean>
</beans>
(4)Bean的获取
IOC容器是对Bean的控制管理,我们现在调用IOC容器将Bean取出。
public class SpringController {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("baens.xml");
HelloSpring hello = (HelloSpring) context.getBean("hello");
System.out.println(hello.toString());
}
}
2、IoC容器的设计
在Spring容器的设计中,有两个主要的容器设计系列,一个是实现BeanFactory接口的简单容器系列,另外一个是ApplicationContext应用上下文系列,它作为容器的高级形态而存在。我们将设计路线的
-
第一条线路:
- BeanFactory接口定义了基本的IoC容器的规范,定义了getBean()、getType()、isSingleton()、isPrototype()等基本方法。
- HierarchicalBeanFactory接口增加了getParentBeanFactory()接口功能,使BeanFactory具备了双亲IOC管理功能。
- ConfigurableBeanFactory接口定义了对BeanFactory的配置功能,配置了Bean后置处理器,实现了简单Ioc的基本功能,实现对工厂的配置以及对bean属性的自动装配。
- DefaultListableBEanfactory实现类是其它许多BeanFactory的扩展基础。
-
第二条线路:
- ApplicationContext接口除了继承HierarchicalBeanFactory、ListableBeanFactory以外,还继承了MessageSource、ResourceLoader、ApplicationEventPublisher接口,即在BeanFactory简单Ioc容器基础上添加了许多高级容器特性。
- ConfigurableApplicationContext接口结合了所有ApplicationContext需要实现的接口。因此大多数的ApplicationContext都要实现此接口。它在ApplicationContext的基础上增加了一系列配置应用上下文的功能。
- AbstractApplicationContext实现类是Spring应用上下文中最重要的一个类,这个抽象类中提供了几乎ApplicationContext的所有操作。主要有容器工厂的处理,事件的发送广播,监听器添加,容器初始化操作refresh()方法,然后就是bean的生成获取方法接口等。是其它许多ApplicationContext应用上下文的扩展基础。
- ClassPathXmlApplication实现类:它是从类的根路径下加载xml配置文件(推荐用这种)。
- FileSystemXmlApplication实现类: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。(但使用不灵活,不推荐)
- AnnotationConfigApplication实现类:当我们使用注解配置容器对象时,需要使用此类来创建spring容器。它用来读取注解。(springboot默认使用这个)
两者装载Bean的区别:
-
BeanFactory:BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;
-
ApplicationContext:ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
3、Bean与BeanDifinition
(1)Bean与BeanDifinition定义和关系
BeanDefinition与Bean的关系, 就好比类与对象的关系。根据BeanDefinition得到的对象就是我们需要的Bean。还记得上面我们的程序,是如何getBean的吗?
ApplicationContext context = new ClassPathXmlApplicationContext("baens.xml");
HelloSpring hello = (HelloSpring) context.getBean("hello");
简简单单两句话,我们获得了Bean。可以看到,我们是通过解析baens.xml文件来初始化Ioc容器,通过容器的getBean方法获得了id为hello的Bean,此处的id就是bean的标识符。我们可以知道bean在xml文件中的形式为
<bean id="hello" class="com.plord.pojo.HelloSpring">
<property name="property" value="Hello,Spring!"/>
</bean>
在Spring中, 是根据配置(XML、注解等), 生成用来描述Bean的BeanDefinition。 类似于Java中描述类的Class。Bean是我们需要的对象,是我们从spring内得到的结果,也就是对象实例。BeanDefinition就是配置文件与最终生成bean之间的桥梁。
BeanDefinition 是定义 Bean 的配置元信息接口,定义了Bean 行为配置信息,作用域、自动绑定模式、生命周期回调、延迟加载、初始方法、销毁方法等。关于Bean的建立我们将在后面继续进行讲解,在讲解前我们先来看看BeanDifinition。
(2)BeanDifinition的设计
- BeanDefinition接口顶级基础接口,用来描述Bean,里面存放Bean元数据,比如Bean类名、scope、属性、构造函数参数列表、依赖的bean、是否是单例类、是否是懒加载等一些列信息。
- AnnotatedBeanDefinition接口:表示注解类型BeanDefinition。有两个重要的属性,AnnotationMetadata,MethodMetadata分别表示BeanDefinition的注解元信息和方法元信息实现了此接口的BeanDefinition可以获取到注解元数据和方法元数据。
- AbstractBeanDefinition类:抽象类统一实现了BeanDefinition定义的一部分操作,可以说是定义了BeanDefinition很多默认的属性。 正是在AbstractBeanDefinition基础上, Spring衍生出了一些列BeaDefinition。
- RootBeanDefinition:代表一个xml,java Config来的BeanDefinition
- ChildBeanDefinition:可以让子BeanDefinition定义拥有从父母哪里继承配置的能力。
- GenericBeanDefinition:spring2.5后注册bean首选的是GenericBeanDefinition。GenericBeanDefinition允许动态的设置父bean.GenericBeanDefinition可以作为RootBeanDefinition与ChildBeanDefinition的替代品。
- AnnotatedGenericBeanDefinition类:表示@Configuration注解注释的BeanDefinition
- ScannedGenericBeanDefinition类:表示@Component、@Service、@Controller等注解注释的Bean类
(3) spring解析xml生成BeanDefinition的过程
参考:https://www.iteye.com/blog/liudeh-009-1966616
spring解析xml生成BeanDefinition对象的过程主要涉及到以下几个类,它们职责分别是:
-
XmlBeanDefinitionReader根据xml的文件路径生成InputStream对象,进而得到InputStream的xml的包装类InputSource对象。
-
DocumentLoader解析InputSource得到Document对象。
-
DefaultBeanDefinitionDocumentReader类委托BeanDefinitionParserDelegate类解析Document对象生成BeanDefinition对象,BeanDefinition对象中包含xml中配置的bean的元信息。
-
生成的BeanDefinition对象最终都会注册到DefaultListableBeanFactory,供后续生成bean对象使用。
(4)Bean的生命周期
首先我们要清楚Bean的四个生命周期,我们将在后续展开对四个生命周期进行扩展探究Bean的一生。
-
实例化 Instantiation
-
属性赋值 Populate
-
初始化 Initialization
-
销毁 Destruction
4、IoC容器初始化
(1)IoC容器的初始化步骤
- Resource定位:指BeanDefinition的资源定位,由ResourceLoader通过统一的Resource接口来完成。
- BeanDefinition载入:把用户定义好的Bean表示成Ioc容器内部的数据结构,即BeanDefinition。
- Beandifinition注册:通过调用BeanDefinitionRegistry接口的实现,把载入过程中解析得到的BeanDefinition向IoC容器进行注册。实际上是在IoC容器内部将BeanDefinition注入到一个HashMap中去,IoC容器通过HashMap来持有和管理BeanDefinition。
(2)Resource定位
参考:https://blog.csdn.net/sinat_34596644/article/details/80394209
- AbstractApplicationContext:
- refresh():在ClassPathXmlApplicationContext的构造器中会调用到AbstractApplicationContext的refresh方法。在初始化ClassPathXmlApplicationContext这个IoC容器时,会通过refresh方法来启动整个调用。
- obtainFreshBeanFactory():在调用AbstractApplicationContext的refresh方法时,该方法中会通过调用refreshBeanFactory和getBeanFactory方法,重新获取一个新的BeanFactory实例,代表IoC容器初始化过程的启动。
- AbstractRefreshableApplicationContext:
- refreshBeanFactory():销毁原BeanFactory,新建DefaultListableBeanFactory实例,并对其进行一些初始化工作。
- AbstractXmlApplicationContext:
- loadBeanDefinitions():上一步创建BeanFactory实例之后,通过该BeanFactory创建XmlBeanDefinitionReader实例,也就是ApplicationContext上下文将BeanDefinition的定位加载工作交付到了XmlBeanDefinitionReader。
- 该方法会调用其同名方法loadBeanDefinitions(XmlBeanDefinitionReader reader),这一步完成了将部分XML文件资源封装为Resource对象,而用户以XML文件路径传入的BeanDefinition资源文件也会在AbstractBeanDefinitionReader的loadBeanDefinitions(String…locations)方法逐一封装为Resource对象,并进行进一步的处理。
- loadBeanDefinitions():上一步创建BeanFactory实例之后,通过该BeanFactory创建XmlBeanDefinitionReader实例,也就是ApplicationContext上下文将BeanDefinition的定位加载工作交付到了XmlBeanDefinitionReader。
(3)BeanDefinition载入
-
XmlBeanDefinitionReader:
- loadBeanDefinitions():这一步主要是通过Resource对象获取XML文件的输入流,继续执行doLoadBeanDefinitions。因为是XML文件格式组织的BeanDefinition,因此AbstractBeanDefinitionReader需要委托到其子类XmlBeanDefinitionReader。
- doLoadBeanDefinitions():通过上一步获取到的输入流和Resource对象,获取Document对象。
- registerBeanDefinitions():初始化DefaultBeanDefinitionDocumentReader对象,将上一步得到的Document对象交由DefaultBeanDefinitionDocumentReader对象解析,并统计解析的BeanDefinition个数。
-
DefaultBeanDefinitionDocumentReader:
- registerBeanDefinitions():获取Document对象的根元素,执行doRegisterBeanDefinitions。
- parseBeanDefinitions():初始化BeanDefinitionParserDelegate实例,递归解析Document。DefaultBeanDefinitionDocumentReader负责读入Document对象,实际将Document解析为BeanDefinition的却是BeanDefinitionParserDelegate对象。
- processBeanDefinition():传入Document对象的单个Element元素,将其交由上一步的BeanDefinitionParserDelegate对象解析,解析后得到BeanDefinitionHolder对象。解析过程较为复杂,有兴趣可以自行跟踪。
(4)Beandifinition的注册
- BeanDefinitionReaderUtils:
- registerBeanDefinition():传入上一步的BeanDefinitionHolder对象,将BeanDefinition注册到IoC容器中。完成的是向IoC容器注册这些BeanDefinition,BeanFactory是以HashMap的结构组织这些BeanDefinition的。可以在DefaultListableBeanFactory中看到此HashMap的定义。
(5)流程图简化版
5、依赖注入
IoC容器初始化过程完成的主要工作是建立BeanDefinition数据映射,在此过程中,并没有对Bean依赖关系进行注入。依赖注入的过程,主要以AbstractBeanFactory的getBean()方法作为依赖注入的起点,最后以BeanWeapper对象的setPropertyValue()来具体实现依赖注入的发生。由于涉及的方法较多,我根据方法调用的三条线路展开依赖注入的过程讲解。
- 1:
- getBean():依赖注入的入口,触发依赖注入的发生。getBean()中会调用doGetBean(),dogetBean()会调用creatBean()。
- creatBean():会进行Bean是否可以实例化或是否可以通过类装载器来载入,若判断成功,会看需要创建的Bean是否配置的PostProcessor,若没有则会调用dogetBean()方法进行Bean的创建()。
- dogetBean():调用createBeanInstance()对Bean进行实例化即Bean的生成(转到2)。然后会调用populateBean()通过已注册的BeanDefinition对需要创建的Bean进行属性的注入(转到3)。
- 2:
- createBeanInstance():如果构造函数的参数使用了Autowire,会调用autowireConstructor进行实例化。若没使用Autowire,则会调用instantiateBean()进行对Bean进行实例化。
- instantiateBean():这里使用CGLIB对Bean进行实例化。
- 3:
- populateBean():这里将取得在BeanDefinition中设置的property值,来源于对BeanDefinition的解析,具体是通过调用applyPropertyValues()来实现,解析后会调用setPropertyValue()最终实现依赖注入的发生。
- applyPropertyValues():若Property值是MutablePropertyValues类型的实例,会直接将property值设置到属性中,若不是则会创建property值的副本,进行进一步解析,具体解析过程由resolveValueIfNecessary()完成。
- resolveValueIfNecessary():根据property值的不同类型,进行不同类型的解析
- setPropertyValue():真正把Bean对象设置到它所依赖的另一个Bean的属性中去的地方,其中处理的属性是各种各样的,依赖注入的发生在Bean Wrapper的setPropertyValue()中实现,具体完成是在Bean WrapperImpl中实现。
6、ApplicationContext与Bean的生命周期
对于普通的 Java 对象来说,它们的生命周期就是:实例化,或该对象不再被使用时通过垃圾回收机制进行回收。
对于 Spring Bean 的生命周期来说:
- 实例化 Instantiation:Bean实例的创建
- 属性赋值 Populate:为Bean实例设置属性
- 初始化 Initialization:调用Bean的初始化方法,应用可以通过IoC容器使用Bean
- 销毁 Destruction:当容器关闭时,调用Bean的销毁方法
我们之前在依赖注入过程中,已经对Bean的实例化和属性赋值做了讲解,所以我们现在简单回顾下实例化和属性赋值过程,然后说明下Bean的初始化和销毁过程。
-
实例化:通过getBean()方法开启,主要通过AbstractAutowireCapableBeanFactory这个类的doCreateBean()调用instantiateBean()方法通过使用CGLIB对Bean进行实例化。
-
属性赋值:主要通过AbstractAutowireCapableBeanFactory这个类的doCreateBean()调用populateBean()方法,即解析得到的BeanDefinition作为依据进行依赖关系的处理,最后通过调用BeanWrapper对象的setPropertyValue()中实现。
-
初始化:
- 在调用Bean初始化方法前,会调用一系列aware接口实现,把相关的BeanName、BeanClassLoader以及BeanFactory注入到Bean中去。
- 若Bean实现了InitializingBean接口,对应的初始化处理会在该接口中的afterPropertiesSet方法中实现,这里触及到对Bean的回调。
- 在执行这个方法前后,会执行BeanPostProcessor的前置处理方法和后置处理方法。
- 若开发者有自定义init-method方法,则会执行用户自定义的初始化方法。
-
销毁:首先会对postProcessBeforeDestruction进行调用,然后调用Bean的destory方法,若开发者配置了destory-method等自定义销毁方法,则会执行该方法,完成Bean的销毁过程。
三、AOP
1、AOP的理解
AOP英文名为Aspect Oriented Programming,意为面向切面编程,通过预编译方式和运行期间动态代理实现程序功能统一维护的一种技术。
在OOP中,以类作为程序的基本单元,通过封装继承的方式达到代码的复用。尽管如此,依然需要在不同类中创建相同的对象,调用相同的属性或方法,对于复杂的系统依然使得代码比较冗余。AOP的出现便是为了解决此问题,AOP中的基本单元是Aspect(切面)。
我们从下图中了解AOP的过程:切面(Aspect)中描述了切入点(Pointcut)和具体实现方法通知(Advice),其中切入点描述了需要被处理的连接点(Joinpoint),连接点指程序运行中的时间点(方法调用或异常发生等)。目标对象(target object)运行时,织入器会根据Aspect中的Pointcut的描述,找到需要执行的时间点(或连接点),在该点动态创建一个切面对象(称为代理Proxy)来代理增强对象,并执行相关方法(即Advice)。
在Spring框架中,默认采用动态代理的方式进行织入(JDK或CGLIB),而AspectJ(基于java语言的AOP框架),采用编译期织入或类装载期织入的方式进行,Spring也可以通过XML或注解的方式整合Aspect实现AOP。
2、动态代理实现方式
假定我们现在需要对数据访问层(DAO)的TestDao的所有方法进行异常处理和日志记录等操作,虽然我们可以将这些操作封装到一个类中,然后在TestDao的每个方法都创建该类的对象并调用这四个方法,但是我们现在不这么做,我们采用动态代理的方式来实现。
① Dao层
public interface TestDao{
public void save();
public void delete();
}
public class TestDaoImpl implements TestDao{
public void save(){
System.out.println("保存");
}
public void delete(){
System.out.println("删除")
}
② 切面类
public class Aspect1{
public void exceptAdvice(){
System.out.println("异常处理");
}
public void logAdvice(){
System.out.println("日志记录");
}
}
我们来看看不同的代理类创建方式。
(1)JDK 动态代理
JDK动态代理是java.lang.reflect.*包提供是方式,它必须借助一个接口才能产生代理对象。对于使用业务接口的类,Spring默认使用JDK动态代理实现AOP。
① 动态代理类
//必须实现java.lang.reflect.InvocationHandler
public class JDKProxy implements InvocationHandler{
//1、声明需要代理对象的接口
private TestDao testDao;
//2、创建代理方法,建立代理关系,返回被代理的增强对象
public Object createProxy(TestDao testDao){
this.testDao = testDao;
//① 类加载器
ClassLoader classLoader = JDKProxy.class.getClassLoader();
//② 被代理对象实现的所有接口
Class[] clazz = testDao.getClass().getInterfaces();
//③ 使用代理类进行增强,返回被增强的代理对象
return Proxy.new.newProxyInstance(classLoader,clazz,this);
}
//3、代理实现方法,程序执行目标方法时被调用
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
//① 创建一个切面
Aspect1 aspect1 = new Aspect();
//前增强
aspect1.exceptAdvice();
//相当于调用testDao中的方法
Object obj = method.invoke(testDao,args);
//后增强
aspect1.logAdvice();
return obj;
}
}
② 测试类
public class TEST1{
public static void main(String[] args){
//创建目标对象
TestDao testDao = new TestDaoImpl();
//创建代理类对象
JDKProxy jdkproxy = new JDKproxy();
//获取增强后的被代理对象
TestDao testDaoAdvicer = (TextDao) jdkproxy.creatProxy(testDao);
//执行方法
testDaoAdvicer.save();
testDaoAdvicer.delete();
}
}
(2)CGLIB动态代理
对于没有提供接口的类,我们采用CGLIB动态代理,这是基于父类的动态代理技术。
CGLIB(Code Generation Library):高性能开源的代码生成包,运用底层字节码技术,对指定目标生成一个子类,并对子类进行增强。
① 动态代理类
//必须实现java.lang.reflect.InvocationHandler
public class CGLIBProxy implements MethodInterceptor{
//1、创建代理方法,建立代理关系,返回被代理的增强对象
public Object createProxy(Object target){
//创建增强类对象
Enhancer enhancer = new Enhancer();
//确定需要增强的类
enhancer.setSuperclass(target.getClass());
//确定代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor的方法
enhancer.setCallback(this);
//返回创建的代理对象
return enhancer.creater();
}
//2、代理实现方法,程序执行目标方法时被调用
@Override
public Object intercept(Object proxy,Method method,Object[] args,MethodProxy methodProxy) throws Throwable{
//① 创建一个切面
Aspect1 aspect1 = new Aspect();
//前增强
aspect1.exceptAdvice();
//相当于调用testDao中的方法
Object obj = methodProxy.invokeSuper(proxy,args);
//后增强
aspect1.logAdvice();
return obj;
}
}
② 测试类
public class TEST1{
public static void main(String[] args){
//创建目标对象
TestDao testDao = new TestDaoImpl();
//创建代理类对象
CGLIBProxy cglibproxy = new CGLIBproxy();
//获取增强后的被代理对象
TestDao testDaoAdvicer = (TextDao) cglibproxy.creatProxy(testDao);
//执行方法
testDaoAdvicer.save();
testDaoAdvicer.delete();
}
}
3、基于XML配置或注解开发AspectJ
(1)基于XML
① 目标对象的类(target class)
public class TestDaoImpl implements TestDao {
public void save() {
System.out.println("saved");
}
public void delete() {
System.out.println("deleted");
}
}
② 切面类(Aspect)
package com.plord.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class Aspect1 {
//前置通知
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("前置通知:模拟权限控制");
System.out.println("目标对象"+joinPoint.getTarget()+"\n被增强的方法:"+joinPoint.getSignature().getName());
}
//后置返回通知
public void afterReturningAdvice(JoinPoint joinPoint){
System.out.println("后置返回通知:模拟删除文件");
System.out.println("目标对象"+joinPoint.getTarget()+"\n被增强的方法:"+joinPoint.getSignature().getName());
}
//环绕通知
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知");
//执行当前目标的方法
Object obj = proceedingJoinPoint.proceed();
System.out.println("通知结束");
return obj;
}
//异常通知
public void throwingAdvice(Throwable e){
System.out.println("异常通知"+"程序执行异常"+e.getMessage());
}
//后置通知(最终通知)
public void after(){
System.out.println("最终通知:模拟释放资源");
}
}
③ 配置文件(application.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册目标对象-->
<bean id="testDao1" class="com.plord.Dao.TestDaoImpl"/>
<!--注册切面-->
<bean id="aspect1" class="com.plord.aspect.Aspect1"/>
<!--AOP配置-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="aspect1">
<!--配置切入点:连接点的集合,通知增强哪些方法-->
<aop:pointcut id="pointcut1" expression="execution(* com.plord.Dao.*.*(..))"/>
<!--关联通知与切入点-->
<aop:before method="beforeAdvice" pointcut-ref="pointcut1"/>
<aop:after-returning method="afterReturningAdvice" pointcut-ref="pointcut1"/>
<aop:around method="aroundAdvice" pointcut-ref="pointcut1"/>
<aop:after-throwing method="throwingAdvice" pointcut-ref="pointcut1" throwing="e"/>
<aop:after method="after" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
</beans>
④ 测试类(TestController)
public class TestController {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
TestDao testDao = (TestDao) applicationContext.getBean("testDao1");
testDao.save();
System.out.println("\n============\n");
testDao.delete();
}
}
(2)基于注解
① 切面
@Aspect
@Component
public class Aspect1 {
@PointCut("execution(* com.plord.Dao.*.*(..))")
private void PointCut(){
}
//前置通知
@Before("PointCut()")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("前置通知:模拟权限控制");
System.out.println("目标对象"+joinPoint.getTarget()+"\n被增强的方法:"+joinPoint.getSignature().getName());
}
//后置返回通知
@AfterReturning("PointCut()")
public void afterReturningAdvice(JoinPoint joinPoint){
System.out.println("后置返回通知:模拟删除文件");
System.out.println("目标对象"+joinPoint.getTarget()+"\n被增强的方法:"+joinPoint.getSignature().getName());
}
//环绕通知
@Around("PointCut()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知");
//执行当前目标的方法
Object obj = proceedingJoinPoint.proceed();
System.out.println("通知结束");
return obj;
}
//异常通知
@AfterThrowing("PointCut()")
public void throwingAdvice(Throwable e){
System.out.println("异常通知"+"程序执行异常"+e.getMessage());
}
//后置通知(最终通知)
@After("PointCut()")
public void after(){
System.out.println("最终通知:模拟释放资源");
}
}
② 目标类
@Repository("testDao")
public class TestDaoImpl implements TestDao {
public void save() {
System.out.println("saved");
}
public void delete() {
System.out.println("deleted");
}
}
③ 配置文件application.xml
<context:component-scan base-package=""/>
<aop:aspectj-autoproxy/>
4、Spring AOP原理剖析与总结
刚才我们已经展示了Spring所提供的三种AOP实现方法,即从JDK自带的动态代理,到Spring整合的CGLIB动态代理,再到后续集成的AspectJ技术。
织入可以简单理解为aspect(切面)应用到目标函数(类)的过程。对于这个过程,一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的。
动态织入:Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术。
静态织入:ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
Spring AOP :遵循AOP规范,特别是以AspectJ(与java无缝整合)为参考,因此在Spring AOP的术语概念上与AspectJ的AOP术语是一样的,如切点(pointcut)定义需要应用通知的目标函数,通知则是那些需要应用到目标函数而编写的函数体,切面(Aspect)则是通知与切点的结合。织入(weaving),将aspect类应用到目标函数(类)的过程,只不过Spring AOP底层是通过动态代理技术。
(1)JDK动态代理
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法
- 创建被代理的类以及接口
- 通过Proxy的静态方法newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
- 通过creatProxy()建立代理关系,调用相关方法
(2)CGLIB动态代理
-
创建一个实现接口MethodInterceptor的类,它必须实现intercept方法
-
创建Enhancer实例;
-
通过setSuperclass方法来设置目标类;
-
通过setCallback方法来设置拦截对象;
-
create方法生成Target的代理类,并返回代理类的实例。