一起写框架-Ioc内核容器的实现-基础功能-ComponentScan(四)

时间:2020-12-15 17:44:01

功能说明

该步骤实现的功能包括:

1. 启动程序时,将@ComponentScan加载的类,创建对象并放在容器里面。

2. 通过ApplicatoinContext的getBean()方法获得容器里面的对象。 (放在下一篇文实现)

实现步骤

1.定义一个扫描注解@ComponentScan

 package ioc.core.annotation;

 import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//表示用于运行时的注解
@Retention(RetentionPolicy.RUNTIME)
//表示只能在类或者接口的上面使用
@Target(value=ElementType.TYPE)
@Documented
public @interface ComponentScan { /**
* 声明一个注解属性用于接收扫描的包路径
* @return
*/
String[] basePackages() default {} ; }

2.定义一个@Configuration标识配置类

package ioc.core.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标识配置类注解的定义
* @author ranger
*
*/
//表示用于运行时的注解
@Retention(RetentionPolicy.RUNTIME)
//表示只能在类或者接口的上面使用
@Target(value=ElementType.TYPE)
@Documented
public @interface Configuration { }

2.定义容器 Context接口

 package ioc.core;
import java.util.Map; /**
* Ioc框架的容器接口
* @author ranger
*
*/
public interface Context { /**
* 用于获得容器中的所有对象
* @return
*/
Map<String,Object> getObjects(); /**
* 用于增加容器中的对象
* @param key
* @param value
*/
void addObject(String key, Object value); }

3.定义容器操作接口ApplicationContext

 package ioc.core;
/**
* Ioc框架的容器操作接口
* @author ranger
*
*/
public interface ApplicationContext { /**
* 通过容器里面的对象名,返回容器中的对象
* @param objectName
* @return
*/
Object getBean(String objectName);
}

4.实现容器

 package ioc.core.impl;

 import java.util.HashMap;
import java.util.Map; import ioc.core.Context; /**
* 实现框架容器,用于存储扫描注解创建的所有对象。
* @author ranger
*
*/
public class ContextImpl implements Context { //使用Map来存储对象,为什么使用Map对象呢?因为预留对象名可以设置的需要。
Map<String,Object> objects=new HashMap<String,Object>(); @Override
public Map<String,Object> getObjects() { return this.objects;
} @Override
public void addObject(String key,Object value) {
objects.put(key, value);
}
}

5.实现扫描包。获得包以及该包子包的所有类的类全名。

--使用到一个工具类从包中读取包和其子包类名--

 package ioc.core.utils;

 import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile; /**
* 本类用于读取包下面的类名
* 来自博客
* http://blog.csdn.net/aust_glj/article/details/53385651
*
*/
public class PackageUtils {
public static void main(String[] args) throws Exception {
String packageName = "ioc.core.annotation";
Set<String> classNames = getClassName(packageName, true);
if (classNames != null) {
for (String className : classNames) {
System.out.println(className);
}
}
} /**
* 获取某包下所有类
* @param packageName 包名
* @param isRecursion 是否遍历子包
* @return 类的完整名称
*/
public static Set<String> getClassName(String packageName, boolean isRecursion) {
Set<String> classNames = null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String packagePath = packageName.replace(".", "/"); URL url = loader.getResource(packagePath);
if (url != null) {
String protocol = url.getProtocol();
if (protocol.equals("file")) {
classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion);
} else if (protocol.equals("jar")) {
JarFile jarFile = null;
try{
jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
} catch(Exception e){
e.printStackTrace();
} if(jarFile != null){
getClassNameFromJar(jarFile.entries(), packageName, isRecursion);
}
}
} else {
/*从所有的jar包中查找包名*/
classNames = getClassNameFromJars(((URLClassLoader)loader).getURLs(), packageName, isRecursion);
} return classNames;
} /**
* 从项目文件获取某包下所有类
* @param filePath 文件路径
* @param className 类名集合
* @param isRecursion 是否遍历子包
* @return 类的完整名称
*/
private static Set<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) {
Set<String> className = new HashSet<String>();
File file = new File(filePath);
File[] files = file.listFiles();
for (File childFile : files) {
if (childFile.isDirectory()) {
if (isRecursion) {
className.addAll(getClassNameFromDir(childFile.getPath(), packageName+"."+childFile.getName(), isRecursion));
}
} else {
String fileName = childFile.getName();
if (fileName.endsWith(".class") && !fileName.contains("$")) {
className.add(packageName+ "." + fileName.replace(".class", ""));
}
}
} return className;
} /**
* @param jarEntries
* @param packageName
* @param isRecursion
* @return
*/
private static Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName, boolean isRecursion){
Set<String> classNames = new HashSet<String>(); while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
if(!jarEntry.isDirectory()){
/*
* 这里是为了方便,先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug
* (FIXME: 先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug)
*/
String entryName = jarEntry.getName().replace("/", ".");
if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) {
entryName = entryName.replace(".class", "");
if(isRecursion){
classNames.add(entryName);
} else if(!entryName.replace(packageName+".", "").contains(".")){
classNames.add(entryName);
}
}
}
} return classNames;
} /**
* 从所有jar中搜索该包,并获取该包下所有类
* @param urls URL集合
* @param packageName 包路径
* @param isRecursion 是否遍历子包
* @return 类的完整名称
*/
private static Set<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) {
Set<String> classNames = new HashSet<String>(); for (int i = 0; i < urls.length; i++) {
String classPath = urls[i].getPath(); //不必搜索classes文件夹
if (classPath.endsWith("classes/")) {continue;} JarFile jarFile = null;
try {
jarFile = new JarFile(classPath.substring(classPath.indexOf("/")));
} catch (IOException e) {
e.printStackTrace();
} if (jarFile != null) {
classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion));
}
} return classNames;
}
}

--容器操作类的公用代码写在AbstractApplicationContext抽象类的构造函数里面,方便以后扩展其他的加载情况--

 package ioc.core.impl;

 import java.util.Iterator;
import java.util.Set; import ioc.core.ApplicationContext;
import ioc.core.Context;
import ioc.core.annotation.ComponentScan;
import ioc.core.annotation.Configuration;
import ioc.core.utils.PackageUtils; public abstract class AbstractApplicationContext implements ApplicationContext{
//声明一个线程变量,存储容器对象,表示同一条线程,一个ApplicationContext只操作一个容器对象。
private ThreadLocal<Context> contexts=new ThreadLocal<Context>(); protected String[] basePackage=null;
/**
* 将容器操作加载创建对象的代码写抽象类里面,这样可以方便以后扩展多种实现。
* @param classType
*/
public AbstractApplicationContext(Class<?> classType) {
//判断配置类是否有Configuration注解
Configuration annotation = classType.getAnnotation(Configuration.class);
if(annotation!=null){
//获得组件扫描注解
ComponentScan componentScan = classType.getDeclaredAnnotation(ComponentScan.class);
//获得包名
this.basePackage = componentScan.basePackages();
//根据包名获得类全限制名
Set<String> classNames = PackageUtils.getClassName(this.basePackage[0], true);
//通过类名创建对象
Iterator<String> iteratorClassName = classNames.iterator();
while(iteratorClassName.hasNext()){
String className = iteratorClassName.next();
try {
//通过类全名创建对象
Object instance = Class.forName(className).newInstance();
//将对象加到容器中,对象名就类全名
this.getContext().addObject(instance.getClass().getSimpleName(),instance);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
} public String[] getBasePackage() {
return basePackage;
} public Context getContext(){
if(contexts.get()==null){
//调用容器
Context context=new ContextImpl();
contexts.set(context);
}
return contexts.get();
} }

----实现AnnotationApplicationContext注解操作类,继承bstractApplicationContext---

注意:这里还没有实现getBean方法。先实现启动程序时,包下面的所有类有没有加入到容器里了--

 package ioc.core.impl;

 public class AnntationApplicationContext extends AbstractApplicationContext {

     public AnntationApplicationContext(Class<?> classType) {
super(classType);
} @Override
public Object getBean(String objectName) { return null;
}
}

测试代码

测试是否可以获得指定扫描包下的类的对象

1.创建一个测试源码包存放测试代码

一起写框架-Ioc内核容器的实现-基础功能-ComponentScan(四)

2. Config类,是一个配置类。里面定义扫描包的路径

 package ioc.core.test.config;

 import ioc.core.annotation.ComponentScan;
import ioc.core.annotation.Configuration; //使用定义@Configuration定义该类是一个配置类
@Configuration
//使用ComponentScan设置扫描包的路径
@ComponentScan(basePackages="ioc.core.test")
public class Config { }

3. 编写一个普通的UserService类测试

package ioc.core.test.service;

/**
* 一个普通的类,用于测试是否可以创建对象
* @author ranger
*
*/
public class UserService { public void login(){
System.out.println("-登录-");
}
}

4. 创建一个AnntationApplicationContextTest测试类

package ioc.core.test;

import org.junit.Test;

import ioc.core.impl.AnntationApplicationContext;
import ioc.core.test.config.Config; public class AnntationApplicationContextTest { @Test
public void constructor(){
try {
AnntationApplicationContext context=new AnntationApplicationContext(Config.class);
//如果可以打印出容器里面的对象,说明成功
System.out.println(context.getContext().getObjects());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }

5.测试结果,获得UserService的对象。成功!

一起写框架-Ioc内核容器的实现-基础功能-ComponentScan(四)