編寫一個自己的IOC容器
阿新 • • 發佈:2021-09-08
寫在最前
這個工程旨在練習Java註解和反射,以及體會依賴注入的原理、過程,不以追求可靠、可用為目的,且閱讀此部落格前應當熟練掌握Java且有一定的Spring使用經驗
預期功能
- 模擬Spring中的Bean註冊、自動裝配
編碼部分
自定義註解部分
模擬Spring中的部分註解
- @Bean註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
/**
* 指定Bean的名字
*/
String name() default "";
}
- @Configuration註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Configuration {
}
- @Import註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Import {
/**
* 指定要引入的配置類
*/
Class<!--?-->[] classes();
}
- @Qualifier註解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Qualifier { /** * 自動裝配時指定需要的Bean的名字 */ String value() default ""; }
- @Autowired註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
/**
* 指定自動裝配的方式,通過型別還是名字
*/
AutoWiredType type() default AutoWiredType.BY_TYPE;
}
- 裝配方式列舉類
public enum AutoWiredType { /** * ByType自動裝配 */ BY_TYPE, /** * ByName自動裝配 */ BY_NAME }
自定義異常類
只要繼承Exception類即可,異常類如下
容器類
首先我們需要一個靜態HashMap變數充當單例Spring容器
/**
* Spring容器
*/
private static HashMap<String, Object> container = null;
然後建立一個向容器中增加Bean的方法addBean
,通過synchronized
關鍵字確保執行緒安全
/**
* 新增Bean到容器
* @param name 名字
* @param o 物件
* @throws BeanExistException 異常
*/
private synchronized static void addBean(String name, Object o) throws BeanExistException {
//不允許重複新增
if (container.containsKey(name)) {
throw new BeanExistException("already exist bean with name:'" + name + "'");
}
container.put(name, o);
}
編寫獲取Bean的方法getBean
,並進行過載,分別使用字串、Class物件做引數
public Object getBean(String name) throws NoSuchNameBeanException {
if (!container.containsKey(name)) {
//如果容器中沒有這個名字的Bean就丟出異常
throw new NoSuchNameBeanException("there is no bean with name:" + name);
}
return container.get(name);
}
@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> requiredType, String name) throws NoSuchTypeBeanException, NoQualifiedBeanException, MultipleQualifiedBeanException {
Set<Map.Entry<String, Object>> entries = container.entrySet();
boolean byType = false;// ByType自動裝配的結果標誌
T bean=null;
for (Map.Entry<String, Object> entry : entries) {
if (requiredType.isAssignableFrom(entry.getValue().getClass())) {
byType = true;// 只要有對應型別的Bean就認為ByType成功
if (name == null || name.trim().isEmpty() || name.equals(entry.getKey())) {// ByType成功後進一步判斷有沒有指定Bean的名字
if (bean != null) {
//如果能找到多個滿足條件的Bean就丟擲異常
throw new MultipleQualifiedBeanException("there is more than one qualified bean with type:"+requiredType.getName());
}
bean = ((T) entry.getValue());
}
}
}
if (bean!=null){// 如果找到了符合條件的Bean就返回
return bean;
}
if (!byType) {// 如果ByType失敗,就丟擲ByType失敗的異常
throw new NoSuchTypeBeanException("there is no bean with type:" + requiredType.getName());
} else {// 如果ByType成功而根據指定name篩選失敗則丟擲沒有滿足條件Bean異常
throw new NoQualifiedBeanException("there is no qualified bean with type:" + requiredType.getName() + ",and with name:" + name);
}
}
編寫初始化容器的方法initContainer
,使用synchronized
關鍵字保證執行緒安全,另外由於我們有一個@Import
註解,需要遞迴呼叫這個初始化方法,所以需要一個HashSet用於記錄已經被解析過的配置類,防止兩個配置類同時在@Import
中引用對方導致無限遞迴和重複定義Bean導致丟擲異常
private synchronized void initContainer(String name) throws Exception {
if (this.alreadyInitClassName==null){
this.alreadyInitClassName=new HashSet<>();
}
if (this.alreadyInitClassName.contains(name)){
return;
}else {
this.alreadyInitClassName.add(name);
}
// 反射載入
Class<?> aClass = Class.forName(name);
// 判斷所選類是否存在@Configuration註解
if (aClass.isAnnotationPresent(Configuration.class)) {
// 建立一個配置類物件
Object config = aClass.newInstance();
// 獲取配置類中所有的方法
Method[] declaredMethods = aClass.getDeclaredMethods();
if (container == null) {
container = new HashMap<>(declaredMethods.length*4/3+1);
}
// 遍歷
for (Method declaredMethod : declaredMethods) {
// 判斷此方法是否存在@Bean註解
if (declaredMethod.isAnnotationPresent(Bean.class)) {
// 如果沒有指定Bean的名字
if ("".equals(declaredMethod.getAnnotation(Bean.class).name())) {
// 就使用方法名作為bean的名字並執行這個方法初始化bean並注入到容器
addBean(declaredMethod.getName(), declaredMethod.invoke(config));
} else {
// 否則使用指定的名字初始化bean並注入到容器
addBean(declaredMethod.getAnnotation(Bean.class).name(), declaredMethod.invoke(config));
}
}
}
}
if (aClass.isAnnotationPresent(Import.class)){
Class<?>[] classes = aClass.getAnnotation(Import.class).classes();
for (Class<?> aClass1 : classes) {
initContainer(aClass1.getName());
}
}
}
然後定義自動裝配方法autowiredInit
,並進行過載,一個接受字串引數,一個接受Class物件引數
public synchronized Object autowiredInit(String name) throws Exception {
// 載入這個要裝配的類的class物件
Class<?> aClass = Class.forName(name);
return autowiredInit(aClass);
}
public synchronized <T> T autowiredInit(Class<T> clazz) throws Exception{
// 構造這個類的例項物件
T o = clazz.newInstance();
// 獲取這個類所有的宣告的變數
Field[] declaredFields = clazz.getDeclaredFields();
// 遍歷這些變數
for (Field declaredField : declaredFields) {
// 判斷這個變數是否有@Autowired修飾
if (declaredField.isAnnotationPresent(Autowired.class)) {
// 修改訪問許可權,可以修改private的變數
declaredField.setAccessible(true);
AutoWiredType type = declaredField.getAnnotation(Autowired.class).type();
if (type==AutoWiredType.BY_TYPE){// 判斷自動裝配的方式
if (declaredField.isAnnotationPresent(Qualifier.class)){
String qualifyName = declaredField.getAnnotation(Qualifier.class).value();
declaredField.set(o,this.getBean(declaredField.getType(),qualifyName));
}else{
declaredField.set(o,this.getBean(declaredField.getType(),null));
}
}else {
// 獲取變數的名字
String name1 = declaredField.getName();
// 獲取對應的Bean
Object bean = getBean(name1);
// 設定值
declaredField.set(o, bean);
}
}
}
// 返回這個被裝配完畢的例項物件
return o;
}
最後編寫建構函式,同樣具有兩個過載
public Container(String name) throws Exception {
initContainer(name);
}
public Container(Class<?> configClass) throws Exception {
initContainer(configClass.getName());
}
進行測試
隨便編寫幾個JavaBean即可,只要具有一些屬性並且能夠正常輸出即可
示例:
public class DataSource {
private String url;
private String username;
private String password;
@Override
public String toString() {
return "DataSource{" +
"url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public DataSource() {
}
public void setUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
其他Bean如下:
編寫service層介面和實現類,驗證控制反轉和依賴倒轉
public interface IDemoService {
/**
* 輸出一段話
*/
void demo();
}
實現類一:
public class DemoServiceImpl implements IDemoService {
@Override
public void demo() {
System.out.println("這是第一種實現");
}
}
實現類二:
@Override
public void demo() {
System.out.println("這是第二種實現");
}
}
編寫多個配置檔案
@Configuration
@Import(classes = {Config2.class,Config3.class})
public class Config {
@Bean(name = "mysql")
public DataSource dataSource(){
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(){
SqlSessionFactory sqlSessionFactory = new SqlSessionFactory();
sqlSessionFactory.setDataSource(dataSource());
return sqlSessionFactory;
}
@Bean
public DataSourceTransactionManager transactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource());
return dataSourceTransactionManager;
}
@Bean
public Encoding encoding(){
Encoding encoding = new Encoding();
encoding.setEncoding("UTF-8");
return encoding;
}
}
@Configuration
// 由於容器類做了判斷,此處不會出現無限遞迴及重複定義Bean的異常
@Import(classes = {Config.class,Config3.class})
public class Config2 {
@Bean
public IDemoService iDemoService2(){
// 註冊第二個實現類
return new AnotherDemoServiceImpl();
}
}
@Configuration
public class Config3 {
@Bean
public IDemoService iDemoService(){
// 註冊第一個實現類
return new DemoServiceImpl();
}
}
編寫啟動類
public class Client {
@Autowired
private DataSource mysql;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
@Qualifier("iDemoService")// 從多個IDemoService型別Bean中通過name指定
private IDemoService service;
public static void main(String[] args) throws Exception {
//使用指定配置檔案初始化容器
Container container = new Container(Config.class);
// 進行自動裝配
Client client = container.autowiredInit(Client.class);
// 呼叫方法
client.service.demo();
System.out.println(client.mysql);
}
}