欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > 万字解析Spring简易版实现方案!

万字解析Spring简易版实现方案!

2025/4/2 20:24:39 来源:https://blog.csdn.net/qq_74092815/article/details/146924575  浏览:    关键词:万字解析Spring简易版实现方案!

什么是IOC (Inversion of control)

控制反转,是面向对象的一种设计原则,可以减少计算机代码之间的耦合度。常见的方式叫做依赖注入,还有另外一种方式叫 依赖查找。通过控制反转,对象在创建的时候,由一个调控系统内所有对象的外界实体将其依赖的所有对象的引用传递给它,也就是依赖注入到对象中。

简而言之,即创建对象的权利 或者说 控制的位置,从Java代码转移到Spring容器。Spring创建对象时,会读取配置文件中的信息,然后使用反射给我们创建好对象之后在容器中存储起来,当我们需要某个对象时,通过id获取对象即可,不需要我们自己去new。

IOC的原理:

  • XML解析配置文件

  • 反射获取实例化对象,放到容器中

  • 采用工厂模式返回Bean对象

其中,IOC接口:

  • BeanFactory接口: IOC容器基本功能接口,是spring内部使用的接口,我们在处理业务时一般不直接使用该接口

  • ApplicationContext 接口: BeanFactory的子接口,提供更多更强大的功能,研发人员一般使用的接口

IOC 好处:

  1. 代码更加简洁,不需要去new 要使用的对象了

  2. 面向接口编程,使用者与具体类,解耦,易扩展、替换实现者

  3. 可以方便进行AOP编程

如果IOC的实现是由我们自己来操作,应该会怎么来?即简易版Spring?

IOC 做了啥?

首先,由表及里分析。想要程序员能够直接拿到bean实例对象,那么肯定是需要有一个对象能够提供获取实例的方法。

那么,我们就可以完成第一步,获取bean方法。通过BeanFactory工厂,提供getBean(String)方法,获取Object对象。

那么,对外可以获取了,怎么才能实现将bean的实例信息提供给我们的工厂呢?

对于很多的实体类,我们可以通过一个中间者,将他们统一起来保管。这个中间者为工厂提供Bean的定义信息。故,BeanDifinition【Bean定义】,作为这个中间者,告诉【Bean工厂】如何创建某类实例。那么再想想,我们需要创建Bean对象,是不是需要让Bean定义携带我们创建对象的方式或方法信息。那创建对象的方法有哪些?构造方法当然是一种,如果是工厂类会涉及两种模式,一种是静态方法,一种是工厂对象的成员方法。

好,根据上图,我们大概可以初步确定BeanDifinition中应该有这些方法

  • getBeanClass():Class 获取类【类中包含类名信息啦!】

  • getFactoryMethodName():Stirng 获取工厂的方法名

  • getFactoryBeanName():String 获取工厂bean实例名称

基本模型就差不多了。

如何将【Bean定义】与【Bean工厂 】联结起来呀?好多Bean定义信息,通通注册到Bean工厂,这个描述似乎是可行的。那么,BeanDifinitionRegistry 来实现Bean定义注册的功能。

【Bean定义注册 】需要有的行为无非两个,一个注册【Bean定义】,一个对外(工厂)提供获取【Bean定义】。

BeanFactory:创建、管理Bean,并且对外提供Bean的实例对象。

BeanDifinition:Bean定义信息

BeanDifitionRegistery:Bean定义信息 注册器

另外,我们可以试着考虑一个事情,在使用Spring的时候,有的对象需要保证全局唯一,有的对象创建过程复杂在另处使用希望可以实现原型。那么,这个需求应该放在哪里实现嘞?

对,Bean定义。

还有,对象有创建,那么自然我们需要考虑初始化、销毁等行为,那也是在【Bean定义】中实现。

结合这些需求,我们Bean定义如下图

接下来,我们需要考虑下Bean工厂的具体实现了。

DefaultBeanFactory【Bean工厂实现类】主要有5个功能点

  • 实现Bean定义信息注册

  • 实现Bean工厂定义的getBean方法

  • 实现初始化方法执行

  • 实现单例

  • 实现容器关闭时候执行单例的销毁。

Bean定义信息注册

有这么几个问题,1、如何存储BeanDifinition?如果beanName重名了咋办?

如何存储BeanDifinition:Map<String,BeanDifinition>

beanName重名?怎么知道重名了,需要判断,新方法就出来了,即containsBeanDifinition(String beanName):Boolean。这里我们有两种方案来应对Bean重名的问题,一种可以是抛异常,一种是可以尝试覆盖。

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionRegistException {/* 问题1:如何存储BeanDefinition?问题2:beanName重名怎么办?我们是设计者,我们来定好规则即可问题3:这里要做哪些事(它的代码逻辑)*/Objects.requireNonNull(beanName, "注册bean需要给入beanName");Objects.requireNonNull(beanDefinition, "注册bean需要给入beanDefinition");// 校验给入的bean是否合法if (!beanDefinition.validate()) {throw new BeanDefinitionRegistException("名字为[" + beanName + "] 的bean定义不合法:" + beanDefinition);}/*Spring中默认是不可覆盖(抛异常),可通过参数 spring.main.allow-bean-definition-overriding: true 来允许覆盖*/if (this.containsBeanDefinition(beanName)) {throw new BeanDefinitionRegistException("名字为[" + beanName + "] 的bean定义已存在:" + this.getBeanDefinition(beanName));}this.beanDefintionMap.put(beanName, beanDefinition);
}

实现Bean工厂的getBean方法

这里其实无非在于需要判断创建对象的类型,如果是单例,需要做额外的处理。如果不是单例,创建实例就好了!

public Object getBean(String name) throws Exception {return this.doGetBean(name);
}protected Object doGetBean(String beanName) throws Exception {Objects.requireNonNull(beanName, "beanName不能为空");/*单例如何实现?单例如何存储?如何保证单例?*/Object instance = singletonBeanMap.get(beanName);if (instance != null) {return instance;}BeanDefinition bd = this.getBeanDefinition(beanName);Objects.requireNonNull(bd, "beanDefinition不能为空");//前面已经判断过   == nullif(bd.isSingleton()) { //如果是单例   DCLsynchronized (this.singletonBeanMap) { //加锁instance = this.singletonBeanMap.get(beanName);if(instance == null){//第二次检查instance = doCreateInstance(bd);this.singletonBeanMap.put(beanName,instance);}}   // volatile}else {instance = doCreateInstance(bd);}return instance;
}private Object doCreateInstance(BeanDefinition bd) throws Exception {Class<?> type = bd.getBeanClass();Object instance = null;if (type != null) {if (StringUtils.isBlank(bd.getFactoryMethodName())) {// 构造方法来构造对象instance = this.createInstanceByConstructor(bd);} else {// 静态工厂方法instance = this.createInstanceByStaticFactoryMethod(bd);}} else {// 工厂bean方式来构造对象instance = this.createInstanceByFactoryBean(bd);}// 执行初始化方法this.doInit(bd, instance);return instance;
}/*** 执行初始化方法* * @param bd* @param instance* @throws Exception*/
private void doInit(BeanDefinition bd, Object instance) throws Exception {// 执行初始化方法if (StringUtils.isNotBlank(bd.getInitMethodName())) {Method m = instance.getClass().getMethod(bd.getInitMethodName(), null);m.invoke(instance, null);}
}

那么能够看到不同的策略处理创建bean对象的过程,这里的代码我们用图来展示,如下

初始化方法的执行

这里就简单了。

/*** 执行初始化方法* * @param bd* @param instance* @throws Exception*/
private void doInit(BeanDefinition bd, Object instance) throws Exception {// 执行初始化方法if (StringUtils.isNotBlank(bd.getInitMethodName())) {Method m = instance.getClass().getMethod(bd.getInitMethodName(), null);m.invoke(instance, null);}
}

实现容器关闭的时候执行单例的销毁

 
@Override
public void close() throws IOException {// 执行单例实例的销毁方法for (Entry<String, BeanDefinition> e : this.beanDefintionMap.entrySet()) {String beanName = e.getKey();BeanDefinition bd = e.getValue();if (bd.isSingleton() && StringUtils.isNotBlank(bd.getDestroyMethodName())) {Object instance = this.singletonBeanMap.get(beanName);try {Method m = instance.getClass().getMethod(bd.getDestroyMethodName(), null);m.invoke(instance, null);} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException| InvocationTargetException e1) {log.error("执行bean[" + beanName + "] " + bd + " 的 销毁方法异常!", e1);}}}
}

在单例创建的时候,注意volatile

这里其实要思考的是为什么单例的时候需要使用volatile;

https://blog.csdn.net/qq_74092815/article/details/146453653

看下上述单例内容,这里我们不关心map对象的初始化过程呀!

单例对象提前初始化以提高运行时效率

public class PreBuildBeanFactory extends DefaultBeanFactory {private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PreBuildBeanFactory.class);public void preInstantiateSingletons() throws Exception {synchronized (this.beanDefintionMap) {for (Map.Entry<String, BeanDefinition> entry : this.beanDefintionMap.entrySet()) {String name = entry.getKey();BeanDefinition bd = entry.getValue();if (bd.isSingleton()) {this.getBean(name);if (log.isDebugEnabled()) {log.debug("preInstantiate: name=" + name + " " + bd);}}}}}
}

完整工厂代码(支持Bean别名、Type类型获取对象)

public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry, Closeable {private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultBeanFactory.class);protected Map<String, BeanDefinition> beanDefintionMap = new ConcurrentHashMap<>(256);private Map<String, Object> singletonBeanMap = new ConcurrentHashMap<>(256);private Map<Class<?>, Set<String>> typeMap = new ConcurrentHashMap<>(256);@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionRegistException {Objects.requireNonNull(beanName, "注册bean需要给入beanName");Objects.requireNonNull(beanDefinition, "注册bean需要给入beanDefinition");// 校验给入的bean是否合法if (!beanDefinition.validate()) {throw new BeanDefinitionRegistException("名字为[" + beanName + "] 的bean定义不合法:" + beanDefinition);}/*Spring中默认是不可覆盖(抛异常),可通过参数 spring.main.allow-bean-definition-overriding: true 来允许覆盖*/if (this.containsBeanDefinition(beanName)) {throw new BeanDefinitionRegistException("名字为[" + beanName + "] 的bean定义已存在:" + this.getBeanDefinition(beanName));}this.beanDefintionMap.put(beanName, beanDefinition);}public void registerTypeMap() throws Exception{for(String name : this.beanDefintionMap.keySet()){Class<?> type = this.getType(name);//映射本类this.registerTypeMap(name,type);//父类this.registerSuperClassTypeMap(name,type);//接口this.registerInterfaceTypeMap(name,type);}}private void registerInterfaceTypeMap(String name, Class<?> type) {Class<?>[] interfaces = type.getInterfaces();if (interfaces.length > 0){for(Class<?> interf : interfaces) {this.registerTypeMap(name, interf);//递归找父接口this.registerInterfaceTypeMap(name,interf);}}}private void registerSuperClassTypeMap(String name, Class<?> type) {Class<?> superClass = type.getSuperclass();if(superClass != null && !superClass.equals(Object.class)){this.registerTypeMap(name,superClass);//递归找父类this.registerSuperClassTypeMap(name,superClass);//找父类实现的接口注册this.registerInterfaceTypeMap(name,superClass);}}private void registerTypeMap(String name, Class<?> type) {Set<String> names2type = this.typeMap.get(type);if(names2type == null){names2type = new HashSet<>();this.typeMap.put(type,names2type);}names2type.add(name);}@Overridepublic Class<?> getType(String name) throws Exception{BeanDefinition bd = this.getBeanDefinition(name);Class<?> type = bd.getBeanClass();if (type != null) {if (StringUtils.isBlank(bd.getFactoryMethodName())) {// 构造方法来构造对象的,Type就是beanClass,不需做什么。} else {// 静态工厂方法方式的,反射获得Method,再获取Method的返回值类型type = type.getDeclaredMethod(bd.getFactoryMethodName(),null).getReturnType();}} else {// 工厂bean方式来构造对象的// 获得工厂Bean的Classtype = this.getType(bd.getFactoryBeanName());// 再获得工厂方法的返回值类型type = type.getDeclaredMethod(bd.getFactoryMethodName(),null).getReturnType();}return type;}@Overridepublic BeanDefinition getBeanDefinition(String beanName) {return this.beanDefintionMap.get(beanName);}@Overridepublic boolean containsBeanDefinition(String beanName) {return this.beanDefintionMap.containsKey(beanName);}@Overridepublic Object getBean(String name) throws Exception {return this.doGetBean(name);}@Overridepublic <T> T getBean(Class<T> type) throws Exception {/*逻辑:1 获得其对应的所有的BeanDefinition2 如果只有一个,直接获取bean实例返回,否则3 遍历找出Primary的4 如果primary没有,或大于1个,抛出异常5 返回Primary的实例*/Set<String> names = this.typeMap.get(type);if(names != null) {if(names.size() == 1){return (T)this.getBean(names.iterator().next());}else {//找PrimaryBeanDefinition bd = null;String primaryName = null;StringBuilder nameStrings = new StringBuilder();for(String name : names){bd = this.getBeanDefinition(name);if(bd != null && bd.isPrimary()) {if(primaryName != null){String mess = type + " 类型的Bean存储多个Primary[" + primaryName + "," + name + "]";log.error(mess);throw new Exception(mess);}else {primaryName = name;}}nameStrings.append(" " + name);}if(primaryName != null){return (T)this.getBean(primaryName);}else {String mess = type + " 类型的Bean存在多个[" + nameStrings + "] 但无法确定Primary";log.error(mess);throw new Exception(mess);}}}return null;}@Overridepublic <T> Map<String, T> getBeansOfType(Class<T> type) throws Exception {Set<String> names = this.typeMap.get(type);if(names != null) {Map<String, T> map = new HashMap<>();for(String name : names){map.put(name,(T) this.getBean(name));}return map;}return null;}protected Object doGetBean(String beanName) throws Exception {Objects.requireNonNull(beanName, "beanName不能为空");/*单例如何实现?单例如何存储?如何保证单例?*/Object instance = singletonBeanMap.get(beanName);if (instance != null) {return instance;}BeanDefinition bd = this.getBeanDefinition(beanName);Objects.requireNonNull(bd, "beanDefinition不能为空");if(bd.isSingleton()) { //如果是单例synchronized (this.singletonBeanMap) { //加锁instance = this.singletonBeanMap.get(beanName);if(instance == null){//第二次检查instance = doCreateInstance(bd);this.singletonBeanMap.put(beanName,instance);}}}else {instance = doCreateInstance(bd);}return instance;}private Object doCreateInstance(BeanDefinition bd) throws Exception {Class<?> type = bd.getBeanClass();Object instance = null;if (type != null) {if (StringUtils.isBlank(bd.getFactoryMethodName())) {// 构造方法来构造对象instance = this.createInstanceByConstructor(bd);} else {// 静态工厂方法instance = this.createInstanceByStaticFactoryMethod(bd);}} else {// 工厂bean方式来构造对象instance = this.createInstanceByFactoryBean(bd);}// 执行初始化方法this.doInit(bd, instance);return instance;}// 构造方法来构造对象private Object createInstanceByConstructor(BeanDefinition bd)throws InstantiationException, IllegalAccessException {try {return bd.getBeanClass().newInstance();} catch (SecurityException e1) {log.error("创建bean的实例异常,beanDefinition:" + bd, e1);throw e1;}}// 静态工厂方法private Object createInstanceByStaticFactoryMethod(BeanDefinition bd) throws Exception {Class<?> type = bd.getBeanClass();Method m = type.getMethod(bd.getFactoryMethodName(), null);return m.invoke(type, null);}// 工厂bean方式来构造对象private Object createInstanceByFactoryBean(BeanDefinition bd) throws Exception {Object factoryBean = this.doGetBean(bd.getFactoryBeanName());Method m = factoryBean.getClass().getMethod(bd.getFactoryMethodName(), null);return m.invoke(factoryBean, null);}/*** 执行初始化方法** @param bd* @param instance* @throws Exception*/private void doInit(BeanDefinition bd, Object instance) throws Exception {// 执行初始化方法if (StringUtils.isNotBlank(bd.getInitMethodName())) {Method m = instance.getClass().getMethod(bd.getInitMethodName(), null);m.invoke(instance, null);}}@Overridepublic void close() throws IOException {// 执行单例实例的销毁方法for (Entry<String, BeanDefinition> e : this.beanDefintionMap.entrySet()) {String beanName = e.getKey();BeanDefinition bd = e.getValue();if (bd.isSingleton() && StringUtils.isNotBlank(bd.getDestroyMethodName())) {Object instance = this.singletonBeanMap.get(beanName);try {Method m = instance.getClass().getMethod(bd.getDestroyMethodName(), null);m.invoke(instance, null);} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException| InvocationTargetException e1) {log.error("执行bean[" + beanName + "] " + bd + " 的 销毁方法异常!", e1);}}}}
}

什么是DI(Dependency injection)

依赖注入。对象之间的依赖由容器在运行期决定,即容器动态的将某个依赖注入到对象之中。说的直白点就是给Bean对象的成员变量赋值。

依赖注入的本质就是赋值。

那,哪些情景需要考虑依赖注入嘞?

  • 有参构造赋值

  • 给某属性赋值(set***)

    • 直接值

    • Bean依赖

直接值,如:基本数据类型、String、数组、集合、map等

构造注入

如何定义构造参数的依赖?也就是我们需要通过构造方法来创建实例,然后对应的构造方法我们需要传入对应的参数。在IoC中我们需要通过反射的方式来处理。那么我们在通过反射操作的时候就需要能获取到对应的构造参数的依赖了,这时我们得分析怎么来存储我们的构造参数的依赖了。构造参数的依赖有两个特点:【数量】、【顺序】

参数可以多个,我们使用list集合存储就可以。直接值直接存储,那Bean依赖的呢?我们是需要有个标识知道哪些参数是Bean对象,这里可以是自定义对象,来维护相关的关系。

BeanReference:用来说明Bean依赖——属性依赖那个类型的Bean

可以根据name判断,也可以通过type类型进行判断。

首先,我们需要在【bean定义】中提供获取构造参数的获取方法。

其次,在BeanFactory实现。

  • 构造方法构建

  • 工厂静态方法

  • 工厂成员方法

// 构造方法来构造对象
private Object createInstanceByConstructor(BeanDefinition bd)throws InstantiationException, IllegalAccessException {// 1. 得到真正的参数值List<?> constructorArgumentValues = bd.getConstructorArgumentValues(); // 2.根据对应的构造参数依赖获取到对应的 Constructor Constructor  constructor = 得到对应的构造方法// 3.用实际参数值调用构造方法创建对应的对象return constructor.newInstance(Object ... 实参值); 
}

如何获取构造器?这里需要反射。

两种对应的类型:精确匹配和父子匹配。

实现逻辑:

  • 先根据参数类型进行精确匹配查找

  • 获取所有的构造方法,遍历构造方法,通过参数数量来进行过滤,再与形参与实参类型比较。

    private Constructor<?> determineConstructor(BeanDefinition bd, Object[] args) throws Exception {/*判定构造方法的逻辑应是怎样的?1 先根据参数的类型进行精确匹配查找,如未找到,则进行第2步查找;2获得所有的构造方法,遍历,通过参数数量过滤,再比对形参类型与实参类型。* */Constructor<?> ct = null;//没有参数,则用无参构造方法if (args == null) {return bd.getBeanClass().getConstructor(null);}// 1 先根据参数的类型进行精确匹配查找Class<?>[] paramTypes = new Class[args.length];int j = 0;for (Object p : args) {paramTypes[j++] = p.getClass();}try {ct = bd.getBeanClass().getConstructor(paramTypes);} catch (Exception e) {// 这个异常不需要处理}if (ct == null) {// 2 没有精确参数类型匹配的,获得所有的构造方法,遍历,通过参数数量过滤,再比对形参类型与实参类型。// 判断逻辑:先判断参数数量,再依次比对形参类型与实参类型outer:for (Constructor<?> ct0 : bd.getBeanClass().getConstructors()) {Class<?>[] paramterTypes = ct0.getParameterTypes();if (paramterTypes.length == args.length) {   //通过参数数量过滤for (int i = 0; i < paramterTypes.length; i++) { //再依次比对形参类型与实参类型是否匹配if (!paramterTypes[i].isAssignableFrom(args[i].getClass())) {continue outer; //参数类型不可赋值(不匹配),跳到外层循环,继续下一个}}ct = ct0;  //匹配上了break outer;}}}if (ct != null) {return ct;} else {throw new Exception("不存在对应的构造方法!" + bd);}}// 静态工厂方法private Object createInstanceByStaticFactoryMethod(BeanDefinition bd) throws Exception {Object[] realArgs = this.getConstructorArgumentValues(bd);Class<?> type = bd.getBeanClass();Method m = this.determineFactoryMethod(bd, realArgs, type);return m.invoke(type, realArgs);}// 工厂bean方式来构造对象private Object createInstanceByFactoryBean(BeanDefinition bd) throws Exception {Object[] realArgs = this.getConstructorArgumentValues(bd);Method m = this.determineFactoryMethod(bd, realArgs, this.getType(bd.getFactoryBeanName()));Object factoryBean = this.doGetBean(bd.getFactoryBeanName());return m.invoke(factoryBean, realArgs);}

缓存?

通过推断也得到了对应的构造方法或者对应的工厂方法,那么我们可以不可以在下次需要再次获取的时候省略掉推导的过程呢?显然我们可以在BeanDefinition中增加缓存方法可以实现这个需求。

循环依赖问题

循环依赖的本质是一样的,就你的完整创建要依赖与我,我的完整创建也依赖于你。相互依赖从而没法完整创建造成失败。

 我们通过构造参数依赖是完全可能出现上面的情况的,构造依赖的情况我们是解决不了的。

 既然解决不了,那么我们在程序中如果出现了,应该要怎么来解决呢?其实我们可以在创建一个Bean的时候记录下这个Bean,当这个Bean创建完成后我们在移除这个Bean,然后我们在getBean的时候判断记录中是否有该Bean,如果有就判断为循环依赖,并抛出异常。数据结构我们可以通过Set集合来处理。

属性注入

通过PropertyValue记录相关属性和值。

在DefaultFactory中,实现属性值的依赖注入。

// 创建好实例对象
// 给属性依赖赋值
this.setPropertyDIValues(bd,instance);
// 执行初始化相关方法
this.doInit(bd,instance);// 具体代码如下
// 给入属性依赖
private void setPropertyDIValues(BeanDefinition bd, Object instance) throws Exception {if (CollectionUtils.isEmpty(bd.getPropertyValues())) {return;}for (PropertyValue pv : bd.getPropertyValues()) {if (StringUtils.isBlank(pv.getName())) {continue;}Class<?> clazz = instance.getClass();Field p = clazz.getDeclaredField(pv.getName());//暴力访问  privatep.setAccessible(true);p.set(instance, this.getOneArgumentRealValue(pv.getValue()));}
}

循环依赖问题——可以解决

提前暴露

    private void doEarlyExposeBuildingBeans(String beanName, Object instance) {Map<String,Object> earlyExposeBuildingBeansMap = earlyExposeBuildingBeans.get();if(earlyExposeBuildingBeansMap == null) {earlyExposeBuildingBeansMap = new HashMap<>();earlyExposeBuildingBeans.set(earlyExposeBuildingBeansMap);}earlyExposeBuildingBeansMap.put(beanName,instance);}

AOP——切面编程

https://blog.csdn.net/qq_74092815/article/details/146550065?spm=1001.2014.3001.5502

AOP【Aspect Oriented Programming】面向切面编程,一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现 日志处理,权限控制,性能检测,事务控制等

AOP实现的原理就是动态代理,在有接口的情况下,使用JDK动态代理,在没有接口的情况下使用cglib动态代理。

如果让自己实现AOP,应该怎么入手?

  AOP[Aspect Oriented Programming] 面向切面编程,在不改变类的代码的情况下,对类方法进行功能的增强。

  • 进行功能进行增强——Advice 通知。(实际增强的逻辑部分称为通知)

  • 对类方法增强,可以选择增强的方法。——Pointcuts 切入点(实际被增强的方法,称之为切入点,表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方)

  • 不改变原有类代码,实现增强。——Weaving 织入

Advice,Pointcuts和Weaving各自的特点

  • Advice

    • 用户性:用户来进行增强逻辑代码的编写

    • 变化性:不同增强需求,有不同的实现

    • 可选时机:可选择在方法的前、后、异常时进行功能增强

    • 多重性:同一个切入点可以有多重增强

  • Pointcut

    • 用户性:用户指定

    • 变化性:灵活指定

    • 多点性:多个点上进行增强

  • Weaving

    • 无入侵性,不改变原来代码

    • 在框架实现

AOP设计:

设计AOP功能,其实就是要设计上面分析出来的相关概念的组件。

Advice、Join points、PointCut、Aspect

Advice:

通知,用户提供。

  1. 我们如何能够识别用户提供的东西呢?(用户在我们写好框架后使用我们的框架)

  2. 如何让我们的代码隔绝用户提供的多变性呢?

关键原则:应对变化——采用面向接口编程。

Advice的特点:可选时机,可选择在方法执行前、后、异常时进行功能的增强

大概有这样的几种情况

  • 前置增强-Before

  • 后置增强-AfterReturn

  • 环绕增强-Around

  • 最终通知-After

  • 异常通知-Throwing

PointCut

切入点是由用户来指定在哪些方法点上进行增强,那么这个哪些方法点如何来表示能满足上面的需求呢?

  1. 指定哪些方法,是不是一个描述信息?

  2. 如何来指定一个方法?

  3. 如果有重载的情况怎么办?

  4. 123要求的其实就是一个完整的方法签名

如何做到多点性和灵活性,在一个描述中指定一类类的某些方法?

  • 某个包下的某个类的某个方法

  • 某个包下的所有类中的所有方法

  • 某个包下的所有类中的do开头的方法

  • 某个包下的以service结尾的类中的do开头的方法

  • .....

  • 上述情景,我们是需要有一个表达式能够灵活的描述上述的信息的!

    • 包名.类名.方法名(参数类型)

    • 上述名称都需要支持模糊匹配

设计的这个表达式将被我们用来决定是否需要对某个类的某个方法进行增强,这个决定过程应该是怎么样的?

execution(public * (. .)) 指定切入点为:任意公共方法。 execution( set (. .)) 指定切入点为:任何一个以“set”开始的方法。 execution( com.xyz.service..(. .)) 指定切入点为:定义在service包里的任意类的任意方法。 execution(* com.xyz.service. ..(. .)) 指定切入点为:定义在service包或者子包里的任意类的任意方法。“..”出现在类名中时, 后面必须跟“”,表示包、子包下的所有类。 execution( .service..(. .)) 指定只有一级包下的serivce子包下所有类(接口)中的所有方法为切入点 execution( . .service..*(. .)) 指定所有包下的serivce子包下所有类(接口)中的所有方法为切入点

类设计:

  1. 首先考虑切入点应该具有的属性--->切入点表达式

  2. 切入点应对外提供什么行为

  3. 切入点被我们设计用来做什么?

  对类和方法进行匹配,切入点应该提供匹配类,匹配方法的行为

  1. 如果在我们设计的框架中要能灵活的扩展切点,我们应该如何设计?

  这又是一个要支持可多变的问题,像通知一样,我们定义一套标准接口,定义好基本行为,面向接口编程,屏蔽掉具体的实现。不管哪些方案,都实现匹配类,匹配方法的接口。

public class AspectJExpressionPointcut implements Pointcut {private static PointcutParser pp = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();private String expression;private PointcutExpression pe;public AspectJExpressionPointcut(String expression) {super();this.expression = expression;pe = pp.parsePointcutExpression(expression);}@Overridepublic boolean matchsClass(Class<?> targetClass) {return pe.couldMatchJoinPointsInType(targetClass);}@Overridepublic boolean matchsMethod(Method method, Class<?> targetClass) {ShadowMatch sm = pe.matchesMethodExecution(method);return sm.alwaysMatches();}public String getExpression() {return expression;}
}

Aspect= Advice+Pointcut

Advisor

为用户提供更简单的外观,Advisor(通知者)组合Advice和Pointcut。

Weaving 织入

织入其实就是要把用户提供的增强功能加到指定的方法上。

1. 在什么时候织入?

  创建Bean实例的时候,在Bean初始化后,再对其进行增强。

2. 如何确定bean要增强?

  对bean类及方法挨个匹配用户配置的切面,如果有切面匹配就是要增强

3.如何实现织入?

  代理方式

这里我们要考虑匹配、织入逻辑写到哪里?是写在BeanFactory中吗?

这时我们要考虑如果我们直接在BeanFactory中来处理,后续如果还有其他的需求是不是也要在BeanFactory中处理呢?这样操作有什么不好的地方呢?

  • BeanFactory代码爆炸,不专情

  • 不易扩展

那我们应该要怎么来设计呢?

我们先来回顾下Bean的生产的过程

在这个过程中, 将来会有更多处理逻辑加入到Bean生产过程的不同阶段。我们现在最好是设计出能让我们后面不用再改BeanFactory的代码就能灵活的扩展。

这时我们可以考虑用观察者模式,通过在各个节点加入扩展点,加入注册机制。

那么在这块我们就应用观察者模式来加入一个Bean的后置处理器 BeanPostProcessor

织入的实现流程:

BeanPostProcessor接口定义行为:初始化之前 & 初始化之后执行的方法。

我们需要在BeanFactory对创建的Bean对象做初始化前后要校验是否需要做相关的增强操作。

BeanFactory要实现相关的Bean增强操作,我们要做的行为就是两方面

  1. 创建相关的BeanPostProcessor,并注册到BeanFactory中

  2. BeanFactory在初始化Bean前后判断是否有相关BeanPostProcessor,如果有做相关的增强处理

判断是否增强?

需要判断该Bean是否满足用户定义的切入点表达式。也就是我们需要简单Bean所属的类和所有方法。然后遍历Advisor。取出advisor中的Pointcut来匹配类和方法。

 
   // class AdvisorAutoProxyCreator@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws Throwable {/*逻辑1 判断Bean是否需要增强2 创建代理来实现增强*///1 判断Bean是否需要增强List<Advisor> matchAdvisors = getMatchedAdvisors(bean, beanName);// 2如有切面切中,创建代理来实现增强if (CollectionUtils.isNotEmpty(matchAdvisors)) {bean = this.createProxy(bean, beanName, matchAdvisors);}return bean;}private List<Advisor> getMatchedAdvisors(Object bean, String beanName) throws Throwable {//第一次执行该方法,先从BeanFactory中得到用户配置的所有切面Advisorif (!gettedAllAdvisors) {synchronized (this) {if (!gettedAllAdvisors) {advisors = this.beanFactory.getBeansOfTypeList(Advisor.class);gettedAllAdvisors = true;}}}//如果没有配置切面if (CollectionUtils.isEmpty(this.advisors)) {return null;}//有配置切面// 得到Bean的类、所有的方法Class<?> beanClass = bean.getClass();List<Method> allMethods = this.getAllMethodForClass(beanClass);// 存放匹配的Advisor的listList<Advisor> matchAdvisors = new ArrayList<>();// 遍历Advisor来找匹配的for (Advisor ad : this.advisors) {if (ad instanceof PointcutAdvisor) {if (isPointcutMatchBean((PointcutAdvisor) ad, beanClass, allMethods)) {matchAdvisors.add(ad);}}}return matchAdvisors;
}// 创建代理对象的操作
private Object createProxy(Object bean, String beanName, List<Advisor> matchAdvisors) throws Throwable {// 通过AopProxyFactory工厂去完成选择、和创建代理对象的工作。return AopProxyFactory.getDefaultAopProxyFactory().createAopProxy(bean, beanName, matchAdvisors, beanFactory).getProxy();
}

如果Bean需要被增强,那么我们就需要创建Bean对应的代理对象了。代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在调用者和目标对象之间起到中介的作用;

代理模式资料

代理实现层设计

 动态代理的实现方式有很多种,如何能够做到灵活的扩展呢?在这里我们同样可以通过 抽象面向接口编程来设计一套支持不同代理实现的代码

  有了上面的设计,然后就是需要考虑代理对象的创建了。

 然后具体的应用Advice增强实现的逻辑为:

总结:

整个过程简单描述应该如下,在【Bean工厂】获取对象的时候,根据是否需要加强从而返回对象,如果需要加强,那么我们得到的对象其实就是代理对象。当方法执行时候,就会触发invoke或interceptor方法,在这个方法中,会调用AopProxyUtils的applyAdvice方法!

将Bean定义配置化

IoC和AOP的功能,但是我们在使用的时候发现我们的调用代码还是非常的繁琐,会给应用者很不好的体验。

  上面的代码很直观的看到重复代码很多,要用户设置的内容也很多,低效而且容易出错,这时我们可以看看在Spring中是怎么处理的呢?

一种是通过XML的配置文件方式

<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="abean" class="com.study.spring.samples.ABean"><constructor-arg type="String" value="abean01" /><constructor-arg ref="cbean" /></bean><bean id="cbean" class="com.study.spring.samples.CBean"><constructor-arg type="String" value="cbean01" /></bean>
</beans>

一种是通过注解的方式来处理

@Component
public class AController{@Autowiredprivate Acc ac;
}

XML方式实现

基于XML方式实现我们需要做什么操作呢?

  • 定义XML规范

  • 要写代码来解析XML,完成Bean定义的注册

注解方式实现

  • 定义一套注解

  • 写代码来扫描、解析注解、完成Bean定义注册

XML方法设计

xml的实现流程

我们自己写个解析器,专门解析xml文件。

注解方式设计

  • 类要不要配置为Bean @Component

  • BeanName Scope和Primary @Scope @Primary

  • 工厂方法 工厂Bean @Bean

  • 初始化方法、销毁方法 @PostConstruct @PreDestory

  • 构造参数依赖 @Autowired @Value

  • 属性依赖 @Qualifier

扫描解析注册操作

  我们定义了相关注解后,谁来实现扫描注解、解析注解并完成Bean定义注册呢

那么,如何实现扫描包下的类

    public void scan(String... basePackages) throws Throwable {if (basePackages != null && basePackages.length > 0) {for (String p : basePackages) {/*1 递归扫描包目录下的.class文件2 组合包路径+class文件名 得到全限定类名3 ClassLoad.load("类名") 得到 Class 对象4 解析Class上的注解,获得Bean定义信息,注册Bean定义*///1 递归扫描包目录下的.class文件Set<File> classFiles = this.doScan(p);//2 得到Class对象,并解析注解、注册Bean定义this.readAndRegisterBeanDefintion(classFiles);}}}
如何解析类上的注解
    private void readAndRegisterBeanDefintion(Set<File> classFiles) throws BeanDefinitionRegistException {for (File classFile : classFiles) {String className = getClassNameFromFile(classFile);try {//加载类Class<?> clazz = this.getClass().getClassLoader().loadClass(className);Component component = clazz.getAnnotation(Component.class);if (component != null) {// 标注了@Component注解String beanName = component.value();if (StringUtils.isBlank(beanName)) {beanName = this.generateBeanName(clazz);}GenericBeanDefinition bd = new GenericBeanDefinition();bd.setBeanClass(clazz);//处理ScopeScope scope = clazz.getAnnotation(Scope.class);if (scope != null) {bd.setScope(scope.value());}//处理primaryPrimary primary = clazz.getAnnotation(Primary.class);if (primary != null) {bd.setPrimary(true);}// 处理构造方法,在构造方法上找@Autowired注解,如有,将这个构造方法set到bd;this.handleConstructor(clazz, bd);//处理方法上的注解(找出初始化、销毁、工厂方法)this.handleMethod(clazz, bd, beanName);// 处理属性依赖this.handlePropertyDi(clazz, bd);// 注册bean定义this.registry.registerBeanDefinition(beanName, bd);}} catch (ClassNotFoundException e) {e.printStackTrace();}}}

ApplicationContext(门面模式)

  通过上面的设计,我们可以实现注解的方式来定义,但是给用户的整体体验还是不是很好,这时我们可以通过外观模式,为框架定义一个更简单的统一使用界面

组合为:

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词