需求說明:
????????自己寫一個簡單的 Spring 容器, 通過讀取類的注解(@Component @Controller@Service @Reponsitory) ,將對象注入到 IOC 容器,自己使用 IO+Annotaion+反射+集合 技術實現
思路分析:
一、新建一個包component并在包下創建bean類
環境配置:導入jar包
新建component包
這里將Servlet層,Service層等等的對象放入component包下,因為手寫的Spring基于注解配置的程序是一個簡化的程序,沒有原生的Spring功能強大,這里就將各個對象放入一個包下面便于io讀取
UserAction類:
package com.study.Spring.component;import org.springframework.stereotype.Controller;
//控制層Servlet
@Controller
public class UserAction {
}
UserComponent類:?
package com.study.Spring.component;import org.springframework.stereotype.Component;
//使用component注解可以將該類掃描放入ioc容器中
@Component
public class UserComponent {
}
UserDao類:?
package com.study.Spring.component;import org.springframework.stereotype.Repository;
//這是持久層、dao
@Repository
public class UserDao {
}
?UserService類:
package com.study.Spring.component;import org.springframework.stereotype.Service;
//這是Service層
@Service
public class UserService {
}
?
二、自定義注解(相當于原配置文件中的component-scan)
新建一個包用于存放自己手寫的程序
創建一個自定義注解:ComponentScan
package com.study.Spring.mySpringByAnnotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 這個自定義的注解的作用相當于原配置文件中的component-scan* @Target(ElementType.TYPE)表示該注解作用的對象* @Retention(RetentionPolicy.RUNTIME)表示該注解的生命周期* String value() default "";表示注解的屬性value用于存放包名(即:要掃描的包路徑),默認是空字符串*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {String value() default "";
}
三、創建一個類代替beans.xml文件
創建一個類:MySpringConfig作用相當于beans.cml文件
package com.study.Spring.mySpringByAnnotation;/*** MySpringConfig這個類作用相當于beans.xml文件* "com.study.Spring.component"這個包名傳給該注解的value屬性*/
@ComponentScan("com.study.Spring.component")
public class MySpringConfig {
}
四、創建MySpringApplicationContext類作為容器對象
這個類相當于容器對象:掃描配置文件,初始化Bean,反射創建Bean,保存Bean都在這里執行
package com.study.Spring.mySpringByAnnotation;import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.io.File;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;/*** 這個類相當于容器對象:掃描配置文件,初始化Bean,反射創建Bean,保存Bean都在這里執行*/
public class MySpringApplicationContext {//容器的屬性:MySpringConfig是配置類的Class對象,用于反射創建注解讀取類名//ioc屬性相當于原生的SingleTonObjects用于存儲單例bean對象private Class MySpringConfig;private final ConcurrentHashMap<String,Object> ioc =new ConcurrentHashMap<>();public MySpringApplicationContext(Class mySpringConfig){this.MySpringConfig=mySpringConfig;//反射獲取自定義的注解對象,再獲取注解中的包名ComponentScan componentScan =(ComponentScan) MySpringConfig.getDeclaredAnnotation(ComponentScan.class);String path = componentScan.value();path= path.replace(".", "/");//根據包名獲取實際工作目錄的路徑,再通過io流獲取掃描的類的絕對路徑
//file:/C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/component//這里拿到類加載器用于獲取實際工作目錄ClassLoader classLoader = MySpringConfig.getClassLoader();URL realPath = classLoader.getResource(path);
// /C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/componentString filePath = realPath.getFile();File file = new File(filePath);//通過目錄獲取目錄下的所有Class文件if (file.isDirectory()){File[] files = file.listFiles();for (File item : files) {String name = item.getName();if (name.contains(".class")){//通過字符串的拼接獲取包下面的全類名path = path.replace("/", ".");String className = name.substring(0, name.indexOf("."));//獲取全類名String fullPath=path+"."+className;
// 再根據Class文件進行一次篩選:判斷是否有四種注解的類才進行創建實例對象并存儲在容器中try {//通過classloader獲取的Class對象時輕量級的,不會初始化時調用靜態方法Class<?> aClass = classLoader.loadClass(fullPath);//根據不同類的Class對象判斷是否有指定的注解if (aClass.isAnnotationPresent(Component.class)||aClass.isAnnotationPresent(Controller.class)||aClass.isAnnotationPresent(Service.class)||aClass.isAnnotationPresent(Repository.class)){//如果存在指定的注解那么就通過反射創建Class對象并創建實例對象Class<?> aClass1 = Class.forName(fullPath);//反射創建實例對象作為valueObject instance = aClass1.newInstance();//默認將首字母是小寫的類名作為keyclassName = StringUtils.uncapitalize(className);//將bean保存到容器中ioc.put(className,instance);}} catch (Exception e) {throw new RuntimeException(e);}}}}}public Object getBean(String id){return ioc.get(id);}
}
解釋:
????????獲取配置文件類MySpringConfig的Class對象,就可以用反射創建annotation注解對象,用注解對象獲取包名,有了包名后可以調用ClassLoader的方法得到實際工作目錄,再根據io流的方法+字符串的拼接得到包下面的各個類的全類名,再通過反射得到各個類的Class對象,再進一步篩選,通過反射Class對象調用isAnnotationPresent方法判斷是否存在指定的注解來選擇性初始化對象,最后將對象存儲在concurrentHashmap中,在MySpringApplicationContext類中添加getBean方法獲取bean對象
五、編寫測試類MySpringApplicationContextTest
package com.study.Spring.mySpringByAnnotation;public class MySpringApplicationContextTest {public static void main(String[] args) {MySpringApplicationContext ioc = new MySpringApplicationContext(MySpringConfig.class);Object userAction = ioc.getBean("userAction");Object userComponent = ioc.getBean("userComponent");Object userDao = ioc.getBean("userDao");Object userService = ioc.getBean("userService");System.out.println(userAction);System.out.println(userComponent);System.out.println(userDao);System.out.println(userService);}
}
運行結果:?
通過調試也可以看出已經初始化了四個bean對象?