首先确保你拥有如下环境或者工具java
而后咱们建立一个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
包下建立两个工具类:ValidateUtil
和ClassUtil
。app
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容器了。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 {
}
复制代码
这四个注解都是只能标注在类上的,他们实际上没有任何做用,只是用来标记这个类的,咱们在后面的类集合中就能够很方便的获取和区分被这些注解标记的类。
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等功能打下基础。
- 从零开始实现一个简易的Java MVC框架(一)--前言
- 从零开始实现一个简易的Java MVC框架(二)--实现Bean容器
- 从零开始实现一个简易的Java MVC框架(三)--实现IOC
- 从零开始实现一个简易的Java MVC框架(四)--实现AOP
- 从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点
- 从零开始实现一个简易的Java MVC框架(六)--增强AOP功能
- 从零开始实现一个简易的Java MVC框架(七)--实现MVC
- 从零开始实现一个简易的Java MVC框架(八)--制做Starter
- 从零开始实现一个简易的Java MVC框架(九)--优化MVC代码
源码地址:doodle