首先就是创建一个普通的Java工程,起名TrayagainSpring
然后在 创建两个包 分别为service、spring。service用来存放bean、配置类等,spring包用来存放我们手撸的spring注解等
在service中创建UserService 类,再创建Test类,在Test类中创建我们的spring容器,此时以配置类的形式去创建容器,在service包中创建AppConfig配置类,在spring包里创建CsApplicationContext类,并提供一个参数与构造方法。
CsApplicationContext类
package com.cs.spring;
public class CsApplicationContext {
private Class configClass;
public CsApplicationContext(Class configClass) {
this.configClass = configClass;
}
}
Test类
package com.cs.service;
import com.cs.spring.CsApplicationContext;
public class Test {
public static void main(String[] args) {
CsApplicationContext context = new CsApplicationContext(AppConfig.class);
}
}
配置类APPConfig启动要去扫描路径,所以在spring包中定义ComponentScan注解,并提供属性等
package com.cs.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() default "";
}
package com.cs.service;
import com.cs.spring.ComponentScan;
@ComponentScan("com.cs.service")
public class AppConfig {
}
而UserService类一般会在类上加@Component注解表示把这个类定义为一个bean,所以此时再去spring包下写一个Component注解
package com.cs.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
package com.cs.service;
import com.cs.spring.Component;
@Component("userService")
public class UserService {
}
此时我们就有了一个spring容器了,但是没啥用处,spring容器中有个getBean()方法,所以此时再去CsApplicationContext中创建一个getBean方法
package com.cs.spring;
public class CsApplicationContext {
private Class configClass;
public CsApplicationContext(Class configClass) {
this.configClass = configClass;
}
public Object getBean(String beanName){
return null;
}
}
此时需要思考下,spring容器里的构造方法和getBean方法需要干什么 ?
我们在外面new一个Spring容器对象,就相当于我们去创建一个spring容器,或者启动一个spring容器,那么在启动spring容器的过程中,spring应该去干什么,他会去干什么?
1.spring扫描底层实现
spring启动首先去扫描,那spring是怎么进行扫描的呢?——通过类上面的@ComponentScan注解指定的路径
通过判断接收到的类,上面有没有ComponentScan注解,
package com.cs.spring;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
public class CsApplicationContext {
private Class configClass;
public CsApplicationContext(Class configClass) {
this.configClass = configClass;
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation
= (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
path = path.replace(".", "/");
ClassLoader classLoader = CsApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileName = f.getAbsolutePath();
String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
className = className.replace("\\", ".");
if (fileName.endsWith(".class")) {
try {
Class<?> clazz = classLoader.loadClass(className);
if (clazz.isAnnotationPresent(Component.class)) {
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
}
public Object getBean(String beanName) {
return null;
}
}
2. 模拟BeanDefinition的生成
? 此时已经扫描到了有哪些bean,此时还不能直接给bean创建出来,因为还要分单例bean,还是多例bean,多例bean是在我们去获取的时候才会去创建,此时看向getBean方法,会根据传给我的beanName去找到对应的是哪个类,但是怎么根据名字去找到对应的类呢?又怎么根据名字判断是单例还是多例?如果是单例从单例池中去拿,如果是多例直接创建一个。
? 此时再去spring包下创建一个Scope注解
spring是首先会先生成一个BeanDefinition对象,在spring包下创建BeanDefinition类,提供两个属性,类型、作用域
package com.cs.spring;
public class BeanDefinition {
private Class type;
private String scope;
public Class getType() {
return type;
}
public void setType(Class type) {
this.type = type;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
总结:我们在扫描的过程间,会拿到很多的类,然后去加载这个类去判断这个类上面有没有Component注解,如果有就相当于定义了一个bean,那么我就去生成一个BeanDefinition对象,因为这个是空的BeanDefinition对象,那么这个BeanDefinition对象的类型是什么,我们可以先设置下,然后再去判断是单例的还是多例的,可以通过去判断这个类上面有没有@Scope注解,如果有就去看看Scope注解里定义的值是什么,直接设置给BeanDefinition对象,如果没有@Scope注解,就默认是单例。此时就真正生成了一个BeanDefinition对象。——然后去创建一个Map给BeanDefinition对象存进去。
3. 手写模拟getBean方法的底层实现
package com.cs.spring;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
public class CsApplicationContext {
private Class configClass;
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
public CsApplicationContext(Class configClass) {
this.configClass = configClass;
if (configClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation
= (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
path = path.replace(".", "/");
ClassLoader classLoader = CsApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileName = f.getAbsolutePath();
String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
className = className.replace("\\", ".");
if (fileName.endsWith(".class")) {
try {
Class<?> clazz = classLoader.loadClass(className);
if (clazz.isAnnotationPresent(Component.class)) {
Component component = clazz.getAnnotation(Component.class);
String beanName = component.value();
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
if (clazz.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotaion = clazz.getAnnotation(Scope.class);
String scope = scopeAnnotaion.value();
beanDefinition.setScope(scope);
}else {
beanDefinition.setScope("Singleton");
}
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("Singleton")){
Object bean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName,bean);
}
}
}
private Object createBean(String beanName,BeanDefinition beanDefinition){
return null;
}
public Object getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null){
throw new NullPointerException();
}else {
String scope = beanDefinition.getScope();
if ("Singleton".equals(scope)){
Object bean = singletonObjects.get(beanName);
if (bean == null){
Object bean1 = createBean(beanName, beanDefinition);
singletonObjects.put(beanName,bean1);
}
return bean;
}else {
return createBean(beanName,beanDefinition);
}
}
}
}
4. 手写bean的创建流程
下面去考虑怎么去创建bean————creatBean()
在createBean(String beanName,BeanDefinition beandefinition),中有两个参数,创建bean,bean是一个对象我们得知道是哪个类,我们可以通过参数beanDefinition对象的getType方法获得类对象,再利用反射通过构造方法再去newInstance创建对象
private Object createBean(String beanName,BeanDefinition beanDefinition){
Class clazz = beanDefinition.getType();
try {
Object instance = clazz.getConstructor().newInstance();
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
此时去测试单例,
测试多例
5. 手写依赖注入
bean的默认初始名
if (beanName.equals("")){
beanName = Introspector.decapitalize(clazz.getSimpleName());
}
看下decapitalize方法的源码
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
此时可以不用在@Component后面加bean的名字,默认就是类名首字母小写
去测试一下
去service包下新建一个OrderService类,加上@Component注解
package com.cs.service;
import com.cs.spring.Component;
@Component
public class OrderService {
}
要实现依赖注入,此时在UserService类中,新增一个属性OrderService,去注入,此时需要再去新建一个@Autowired注解
package com.cs.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
此时就可以UserService中使用@Autowired注解,但此时OrderService是否有值呢?我们去测试一下
package com.cs.service;
import com.cs.spring.Autowired;
import com.cs.spring.Component;
import com.cs.spring.Scope;
@Component("userService")
@Scope("Prototype")
public class UserService {
@Autowired
private OrderService orderService;
public void test(){
System.out.println(orderService);
}
}
在Test中测试
可以发现,此时依赖还是没有注入的,那么依赖注入是在哪里完成的呢?
应该在创建bean的时候就去给他添加属性,也就是在createBean方法中,此处也就是在模拟bean的生命周期,bean的创建,第一个步骤就是先实例化得到一个对象,第二个步骤就是给对象进行依赖注入,
一个对象里面,或者其对应的类里面有很多的属性,我们只需要去给那些加了@Autowired的属性赋值,
此时OrderService就有值了,
6.BeanName 的回调
可能不知道某个bean的名字,或者名字呗其他机制给更改了,那么怎么能最终知道这个bean的名字呢?其实这个bean的名字是由spring生成出来的,最终需要spring来告诉我们当前bean的名字是什么,这就需要一种回调,Aware回调,这里需要一个接口
package com.cs.spring;
public interface BeanNameAware {
public void setBeanName(String beanName);
}
我们的bean要去实现这个接口
package com.cs.service;
import com.cs.spring.Autowired;
import com.cs.spring.BeanNameAware;
import com.cs.spring.Component;
import com.cs.spring.Scope;
@Component("userService")
public class UserService implements BeanNameAware {
@Autowired
private OrderService orderService;
private String beanName;
public void test(){
System.out.println(orderService);
}
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}
那么spring在什么时候来调用这个setBeanName方法呢?也就是我们刚刚的依赖注入完成后,spring会去判断 之前生成的对象是否实现了BeanNameAware这个接口,如果实现了 就给对象强制转换成这个接口类型,然后直接去调用你的方法
7.手写模拟Spring初始化机制
|