文章目录
上篇文章<<浅析Spring注解实例化实现机制>>,我们介绍了Spring是如何通过注解来创建实例的,但是并没有通过Scope注解实现单/多例模式,也没有通过Autowired注解来实现依赖注入的问题,那今天我们就用代码实现Spring是如何解决这两个问题的。
1、思路分析
2、代码实现
2.1 自定义注解
ComponentScan
package com.zhl.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 16:26
* @Desc: 相当于扫描包的标签 <context:component-scan base-package="xxxx"/>
* 这里的value需要我们传入要扫描的包路径
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
String value() default "";
}
Component
package com.zhl.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 16:58
* @Desc:
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
Scope
package com.zhl.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 17:12
* @Desc: 指定bean的作用范围 singleton prototype
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
// 指定singleton 还是 prototype
String value() default "";
}
Autowired
package com.zhl.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 18:02
* @Desc:
**/
@Target({ElementType.FIELD,ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
boolean required() default true;
}
2.2 Bean对象
MonsterDao
package com.zhl.spring.component;
import com.zhl.spring.annotation.Component;
/**
* TODO
* @Author zhl
* @Date 2023/4/1 16:30
* @Desc:
**/
@Component
public class MonsterDao {
public void sayHi(){
System.out.println("hello~~,sir");
}
}
MonsterService
/**
* Author: zhl
* Date: 2023/4/1 16:31
* Desc:
*/
package com.zhl.spring.component;
import com.zhl.spring.annotation.Autowired;
import com.zhl.spring.annotation.Component;
import com.zhl.spring.annotation.Scope;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 16:31
* @Desc:
**/
@Component(value = "service01")
@Scope(value = "prototype")
public class MonsterService {
@Autowired
private MonsterDao monsterDao;
public void sayHello(){
monsterDao.sayHi();
}
}
2.3 创建容器类ZhlSpringApplicationContext
/**
* Author: zhl
* Date: 2023/4/1 16:33
* Desc:
*/
package com.zhl.spring.ioc;
import com.zhl.spring.annotation.Autowired;
import com.zhl.spring.annotation.Component;
import com.zhl.spring.annotation.ComponentScan;
import com.zhl.spring.annotation.Scope;
import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 16:33
* @Desc: 这个类相当于IOC容器,传入配置类的class对象,获取到包路径然后进行容器的初始化
**/
public class ZhlSpringApplicationContext {
private Class configClass;
private final ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
public void BeanDefinitionByScan(Class configClass){
this.configClass = configClass;
// 1. 获取注解的value值
// 1.1 首先获取到注解对象
ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
String value = componentScan.value(); // value=com.zhl.spring.component
// 2. 通过类加载器拿到资源路径
ClassLoader classLoader = ZhlSpringApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(value.replace(".", "/"));
String resourcePath = resource.getPath(); // /C:/App/develop/spring-learning/spring5-zhl/target/classes/com/zhl/spring/component
// 3. 通过资源路径拿到每个bean的绝对路径
File file = new File(resourcePath);
if (file.isDirectory()){
File[] files = file.listFiles();
for (File document : files) {
String absolutePath = document.getAbsolutePath(); // C:\App\develop\spring-learning\spring5-zhl\target\classes\com\zhl\spring\component\MonsterDao.class
// 4. 通过绝对路径拿到每个bean的类名
String className = absolutePath.substring(absolutePath.lastIndexOf("\\") + 1, absolutePath.indexOf(".class"));
// 5. 将包路径和类名进行拼接得到全类名,为下面反射处理准备
String fullClassName = value + "." + className; // com.zhl.spring.component.MonsterDao
try {
// 6. 将包路径下所有的bean进行反射
Class<?> aClass = classLoader.loadClass(fullClassName);
// 7. 根据bean的class对象判断bean上是否有Component注解,
// 在这里我们只举例component注解,其余Service Repository注解原理和Component一样
if (aClass.isAnnotationPresent(Component.class)){
// 8. 如果有Component注解,我们要取出它的value值当作容器的key
Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
// 9. 如果Component注解没有传入值,那么将类名小写当做key
if ("".equals(beanName)){
beanName= StringUtils.uncapitalize(className);
}
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setaClass(aClass);
// 10. 判断bean上是否注有Scope注解
if (aClass.isAnnotationPresent(Scope.class)){
Scope scopeAnnotation = aClass.getDeclaredAnnotation(Scope.class);
String scopeValue = scopeAnnotation.value();
if ("".equals(scopeValue)){
scopeValue = "singleton";
}
if ("singleton".equals(scopeValue) || "prototype".equals(scopeValue)){
beanDefinition.setScope(scopeAnnotation.value());
}else {
new Throwable("注解Scope属性值错误~~");
}
}else { // 如果没有标注Scope注解,默认创建单例对象
beanDefinition.setScope("singleton");
}
// 11. 将bean信息放入beanDefinitionMap中
beanDefinitionMap.put(beanName,beanDefinition);
// 12. 如果Scope="singleton", 那么我们实例化对象,放入单例池中
if (beanDefinition.getScope().equals("singleton")){
singletonObjects.put(beanName,createBean(beanDefinition));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 在容器的构造方法中要实现两个目标
* 1、把标有注解的bean的信息放入beanDefinitionMap中
* 2、如果是scope="singleton",那就是创建单例对象,并放入singletonObjects中,
* 如果scope="prototype",在genBean时创建对象
* @param configClass 传入ZhlSpringConfig 的class对象
*/
public ZhlSpringApplicationContext(Class configClass) {
// 1.容器初始化----把bean信息放入BeanDefinitionMap中
BeanDefinitionByScan(configClass);
}
public Object createBean(BeanDefinition beanDefinition){
// 1. 获取class对象
Class aClass = beanDefinition.getaClass();
Object instance = null;
try {
instance = aClass.newInstance();
// 2. 如果存在依赖注入,遍历所有字段,完成注入
for (Field declaredField : aClass.getDeclaredFields()) {
// 3. 判断当前字段是否标有Autowired注解
if (declaredField.isAnnotationPresent(Autowired.class)){
// 4. 获取字段名
String beanName = declaredField.getName();
// 5. 根据字段名获取bean对象
Object bean = getBean(beanName);
// 6. 完成组装
declaredField.setAccessible(true);
declaredField.set(instance,bean);
}
}
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return instance;
}
public Object getBean(String key){
if (beanDefinitionMap.containsKey(key)){
BeanDefinition beanDefinition = beanDefinitionMap.get(key);
if ("singleton".equals(beanDefinition.getScope())){
return singletonObjects.get(key);
}else {
return createBean(beanDefinition);
}
}
return null;
}
}
BeanDefinition
/**
* Author: zhl
* Date: 2023/4/1 16:35
* Desc:
*/
package com.zhl.spring.ioc;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 16:35
* @Desc: 用于封装记录bean的信息
**/
public class BeanDefinition {
private Class aClass;
private String scope;
public BeanDefinition() {
}
public BeanDefinition(Class aClass, String scope) {
this.aClass = aClass;
this.scope = scope;
}
public Class getaClass() {
return aClass;
}
public void setaClass(Class aClass) {
this.aClass = aClass;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
@Override
public String toString() {
return "BeanDefinition{" +
"aClass=" + aClass +
", scope='" + scope + '\'' +
'}';
}
}
ZhlSpringConfig
/**
* Author: zhl
* Date: 2023/4/1 16:27
* Desc:
*/
package com.zhl.spring.ioc;
import com.zhl.spring.annotation.ComponentScan;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 16:27
* @Desc: 这是一个配置类,作用相当于Spring的ApplicationContext.xml 容器配置文件
**/
@ComponentScan(value = "com.zhl.spring.component")
public class ZhlSpringConfig {
}
2.4 测试类
/**
* Author: zhl
* Date: 2023/4/1 16:38
* Desc:
*/
package com.zhl.spring;
import com.zhl.spring.component.MonsterDao;
import com.zhl.spring.component.MonsterService;
import com.zhl.spring.ioc.ZhlSpringApplicationContext;
import com.zhl.spring.ioc.ZhlSpringConfig;
/**
* TODO
*
* @Author zhl
* @Date 2023/4/1 16:38
* @Desc: 测试类
**/
public class AppMain {
public static void main(String[] args) {
ZhlSpringApplicationContext ioc = new ZhlSpringApplicationContext(ZhlSpringConfig.class);
System.out.println("测试单例多例对象的创建");
MonsterDao monsterDao1 = (MonsterDao) ioc.getBean("monsterDao");
MonsterDao monsterDao2 = (MonsterDao) ioc.getBean("monsterDao");
System.out.println(monsterDao1);
System.out.println(monsterDao2);
System.out.println("------------------------------------");
MonsterService monsterService1 = (MonsterService) ioc.getBean("service01");
MonsterService monsterService2 = (MonsterService) ioc.getBean("service01");
System.out.println(monsterService1);
System.out.println(monsterService2);
System.out.println("测试依赖注入");
MonsterService monsterService = (MonsterService) ioc.getBean("service01");
monsterService.sayHello();
}
}
测试结果:
测试单例多例对象的创建
com.zhl.spring.component.MonsterDao@49476842
com.zhl.spring.component.MonsterDao@49476842
------------------------------------
com.zhl.spring.component.MonsterService@2626b418
com.zhl.spring.component.MonsterService@5a07e868
测试依赖注入
hello~~,sir
Process finished with exit code 0
可以看到MonsterService类我们标注了@Scope(value = “prototype”)注解,返回的对象也是2个不同的对象,它是在每次getBean时,重新创建对象然后返回;MonsterDao因为Scope的值等于"singleton",所以返回的对象是一样的,因为它是在容器初始化的时候就进行了对象实例化,放在了singletonObjects中,genBean时只需要从singletonObjects获取返回即可。另外依赖注入也是测试成功的,这个主要是在创建对象的时候用到了反射,上面的代码中写的很详细哦~~