從零開始實現一個簡易的Java MVC框架(二)--實現Bean容器

項目準備

首先確保你擁有如下環境或者工具java

  • idea
  • java 8
  • maven 3.3.X
  • lombok插件

而後咱們建立一個maven工程,編寫pom.xml引入一些須要的依賴git

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <slf4j-api.version>1.7.25</slf4j-api.version>
    <lombok.version>1.16.20</lombok.version>
</properties>
<dependencies>
    <!-- SLF4J -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j-api.version}</version>
    </dependency>
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

目前只須要lombok和log4j兩個依賴就能夠完成前面幾個功能的實現,其餘須要的依賴等到後面須要的時候再加。github

接着把項目一些基本的包結構建立一下,以下圖spring

resources文件夾下的log4j.properties文件爲log4j輸出格式化參數,你們能夠根據本身的喜愛和需求編寫,我本身的只是爲了方便調試使用的,下面是我本身的。apache

### 設置###
log4j.rootLogger = debug,stdout
### 輸出信息到控制擡 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %c %d{ISO8601} -- %p -- %m%n

建立工具類

爲了方便後續代碼的編寫,咱們先建立工具類。api

com.zbw.util包下建立兩個工具類:ValidateUtilClassUtilapp

ValidateUtil主要負責屬性的驗證,這個類的完整代碼就不貼了,就是檢查各類類型的值是否爲空或者是否不爲空。框架

/**
 * 驗證相關工具類
 */
public final class ValidateUtil {

    /**
     * Object是否爲null
     */
    public static boolean isEmpty(Object obj) {
        return obj == null;
    }

    /**
     * String是否爲null或""
     */
    public static boolean isEmpty(String obj) {
        return (obj == null || "".equals(obj));
    }
    
    ...

    /**
     * Object是否不爲null
     */
    public static boolean isNotEmpty(Object obj) {
        return !isEmpty(obj);
    }

    /**
     * String是否不爲null或""
     */
    public static boolean isNotEmpty(String obj) {
        return !isEmpty(obj);
    }

    ...
}

ClassUtil主要是Class的一些相關操做。這其中除了一些類經常使用的實例反射等操做,還有一個重要方法就是getPackageClass(),這個方法會遞歸遍歷傳入的包名下的全部類文件,並返回一個Set<Class<?>>。等一下在實現Bean容器的時候就會使用這個方法來掃描獲取對應包下的全部類文件。maven

/**
 * 類操做工具類
 */
@Slf4j
public final class ClassUtil {

    /**
     * file形式url協議
     */
    public static final String FILE_PROTOCOL = "file";

    /**
     * jar形式url協議
     */
    public static final String JAR_PROTOCOL = "jar";

    /**
     * 獲取classLoader
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 獲取Class
     */
    public static Class<?> loadClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error("load class error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 實例化class
     */
    @SuppressWarnings("unchecked")
    public static <T> T newInstance(String className) {
        try {
            Class<?> clazz = loadClass(className);
            return (T) clazz.newInstance();
        } catch (Exception e) {
            log.error("newInstance error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 實例化class
     */
    @SuppressWarnings("unchecked")
    public static <T> T newInstance(Class<?> clazz) {
        try {
            return (T) clazz.newInstance();
        } catch (Exception e) {
            log.error("newInstance error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 設置類的屬性值
     */
    public static void setField(Field field, Object target, Object value) {
        setField(field, target, value, true);
    }

    /**
     * 設置類的屬性值
     */
    public static void setField(Field field, Object target, Object value, boolean accessible) {
        field.setAccessible(accessible);
        try {
            field.set(target, value);
        } catch (IllegalAccessException e) {
            log.error("setField error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 獲取包下類集合
     */
    public static Set<Class<?>> getPackageClass(String basePackage) {
        URL url = getClassLoader()
                .getResource(basePackage.replace(".", "/"));
        if (null == url) {
            throw new RuntimeException("沒法獲取項目路徑文件");
        }
        try {
            if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)) {
                // 若爲普通文件夾,則遍歷
                File file = new File(url.getFile());
                Path basePath = file.toPath();
                return Files.walk(basePath)
                        .filter(path -> path.toFile().getName().endsWith(".class"))
                        .map(path -> getClassByPath(path, basePath, basePackage))
                        .collect(Collectors.toSet());
            } else if (url.getProtocol().equalsIgnoreCase(JAR_PROTOCOL)) {
                // 若在 jar 包中,則解析 jar 包中的 entry
                JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                return jarURLConnection.getJarFile()
                        .stream()
                        .filter(jarEntry -> jarEntry.getName().endsWith(".class"))
                        .map(ClassUtil::getClassByJar)
                        .collect(Collectors.toSet());
            }
            return Collections.emptySet();
        } catch (IOException e) {
            log.error("load package error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 從Path獲取Class
     */
    private static Class<?> getClassByPath(Path classPath, Path basePath, String basePackage) {
        String packageName = classPath.toString().replace(basePath.toString(), "");
        String className = (basePackage + packageName)
                .replace("/", ".")
                .replace("\\", ".")
                .replace(".class", "");
        return loadClass(className);
    }

    /**
     * 從jar包獲取Class
     */
    private static Class<?> getClassByJar(JarEntry jarEntry) {
        String jarEntryName = jarEntry.getName();
        // 獲取類名
        String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
        return loadClass(className);
    }
}

實現Bean容器

如今開始能夠實現Bean容器了。ide

基礎註解

在spring中咱們老是用各類註解去標註咱們的組件,如controller等。因此咱們也要先寫一些註解來標註一些必要的組件。在zbw.core包下再建立一個annotation包,而後再建立四個最基本的組件.

// Component註解,用於標記組件
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
// Controller註解,用於標記Controller層的組件
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
// Repository註解,用於標記Dao層的組件
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
// Service註解,用於標記Service層的組件
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}

這四個註解都是隻能標註在類上的,他們實際上沒有任何做用,只是用來標記這個類的,咱們在後面的類集合中就能夠很方便的獲取和區分被這些註解標記的類。

BeanContainer

Bean容器實際上就是存放全部Bean的地方,即Class以及相關信息對應其實體的容器,爲何稱之爲'Bean'呢,由於在spring中,定義Class信息和實例的東西叫BeanDefinition。這是一個接口,他有一個模板類AbstractBeanDefinition,這裏面就有一個beanClass變量存放Class類和propertyValues變量存放類屬性,以及不少類相關參數和初始化之類的參數。你們能夠去spring中看看,spring的全部都是依賴於這個Bean生成的,能夠說這是spring的基石。

瞭解到這個之後接下來就能夠開始編寫Bean容器了,在zbw.core包下建立一個類叫BeanContainer

/**
 * Bean容器
 */
@Slf4j
public class BeanContainer {
    /**
     * 存放全部Bean的Map
     */
    private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();

    /**
     * 獲取Bean實例
     */
    public Object getBean(Class<?> clz) {
        if (null == clz) {
            return null;
        }
        return beanMap.get(clz);
    }

    /**
     * 獲取全部Bean集合
     */
    public Set<Object> getBeans() {
        return new HashSet<>(beanMap.values());
    }

    /**
     * 添加一個Bean實例
     */
    public Object addBean(Class<?> clz, Object bean) {
        return beanMap.put(clz, bean);
    }

    /**
     * 移除一個Bean實例
     */
    public void removeBean(Class<?> clz) {
        beanMap.remove(clz);
    }

    /**
     * Bean實例數量
     */
    public int size() {
        return beanMap.size();
    }

    /**
     * 全部Bean的Class集合
     */
    public Set<Class<?>> getClasses() {
        return beanMap.keySet();
    }

    /**
     * 經過註解獲取Bean的Class集合
     */
    public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation) {
        return beanMap.keySet()
                .stream()
                .filter(clz -> clz.isAnnotationPresent(annotation))
                .collect(Collectors.toSet());
    }

    /**
     * 經過實現類或者父類獲取Bean的Class集合
     */
    public Set<Class<?>> getClassesBySuper(Class<?> superClass) {
        return beanMap.keySet()
                .stream()
                .filter(superClass::isAssignableFrom)
                .filter(clz -> !clz.equals(superClass))
                .collect(Collectors.toSet());
    }
}

咱們不須要像spring那樣存放不少的信息,因此用一個Map來存儲Bean的信息就行了。Map的Key爲Class類,Value爲這個Class的實例Object。配合getBean(),addBean()等方法就能夠很方便的操做Class和它的實例。

然而如今這個Map裏尚未存聽任何的Bean數據,因此編寫一個loadBeans()方法來初始化加載Bean。

首先在BeanContainer中添加一個變量isLoadBean和一個常量BEAN_ANNOTATION

//BeanContainer
...

/**
* 是否加載Bean
*/
private boolean isLoadBean = false;

/**
* 加載bean的註解列表
*/
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION 
= Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);

...

而後編寫loadBeans()方法去加載被BEAN_ANNOTATION中的註解類註解的類,以及對應的實例。經過剛纔的ClassUtil.getPackageClass(basePackage)獲取咱們項目下全部的Class,而後判斷該Class是否被BEAN_ANNOTATION中註解類註解,若是有就說明該Class是一個Bean,對其實例化而且放入Map中。

//BeanContainer
...


/**
* 掃描加載全部Bean
*/
public void loadBeans(String basePackage) {
    if (isLoadBean()) {
        log.warn("bean已經加載");
        return;
    }

    Set<Class<?>> classSet = ClassUtil.getPackageClass(basePackage);
    classSet.stream()
        .filter(clz -> {
            for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
                if (clz.isAnnotationPresent(annotation)) {
                    return true;
                }
            }
            return false;
        })
        .forEach(clz -> beanMap.put(clz, ClassUtil.newInstance(clz)));
    isLoadBean = true;
}

/**
* 是否加載Bean
*/
public boolean isLoadBean() {
    return isLoadBean;
}
...

最後,爲了可以保證整個項目全局Bean的惟一性,咱們要保證這個BeanContainer是惟一的,將該類單例化。

經過lombok的註解@NoArgsConstructor(access = AccessLevel.PRIVATE)生成私有構造函數,再用內部枚舉生成惟一的BeanContainer實例。

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanContainer {
    /**
     * 獲取Bean容器實例
     */
    public static BeanContainer getInstance() {
        return ContainerHolder.HOLDER.instance;
    }
    
    ...
    
   private enum ContainerHolder {
        HOLDER;
        private BeanContainer instance;

        ContainerHolder() {
            instance = new BeanContainer();
        }
    }
}

至此,這個Bean容器就完成了。咱們能夠經過loadBeans()方法初始化Bean,而後能夠經過getBean(),addBean()removeBean()等方法去操做這個Bean,爲後面的IOC,AOP等功能打下基礎。


源碼地址:doodle

原文地址:從零開始實現一個簡易的Java MVC框架--實現Bean容器

相關文章
相關標籤/搜索