大家知道,spring依赖注入可以通过xml和annotation两种方式实现,还提供了自动扫描类的功能,这样大大简化了开发。今天也闲着没事,也实现了类似的功能。废话少说,直接上码:
先说明下要使用到的jar包:dom4j.jar和jaxen.jar(读取配置文件),junit.jar(单位测试),log4j.jar和commons-logging.jar(日志记录)。
1,类似spring的@Service注解
/**
* 自动扫描类到容器中
* @author zcl
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
public String value() default "";
}
2,@Resource注解
/**
* 通过此注解实现注入的功能
* @author zcl
*
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {
public String name() default "";
}
3, 配置文件的格式类似这样:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="www.zcl.com.cn"> <bean id="logService" class="cn.zcl.spring.service.impl.LogServiceImpl" /> <bean id ="userService" class="cn.zcl.spring.service.impl.UserServiceImpl"> <property name="logService" ref="logService"/> </bean> <!-- 实现自动扫描特定包下类的功能,可以配置多个 <scan package="cn.zcl.spring"/> --> </beans>
4,封装配置文件中的<property name="" ref=""/>的类:
/**
* 封装<property name="" ref=""/>的对象
* @author zcl
*
*/
public class BeanProperty {
private String name;
private String ref;
public BeanProperty(String name, String ref) {
this.name = name;
this.ref = ref;
}
//省略setter与getter()方法,请自己补上(见源码)
<bean id="" class=""><property name="" ref=""/></bean的类
/**
* 存放形如:
* <bean id="xx" class="xx">
* <property name="xx" ref="xx"/>
* </bean>
* @author zcl
*
*/
public class BeanDefinition {
private String id;//存放id属性值
private String className;//存放class属性值
//存放对应的属性值
private List<BeanProperty> props = new ArrayList<BeanProperty>();
public BeanDefinition(String id, String className) {
this.id = id;
this.className = className;
}
//省略setter与getter()方法,请自己补上(见源码)
6, 之后就是最重要的BeanFactory了
由于代码量比较多。我分步描述
1)定义成员变量:
public class BeanFactory {
private static final Log log = LogFactory.getLog(BeanFactory.class);
/** 存放从配置文件中读取的bean的配置信息 */
private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();
/** 存放初始化的bean对象,其中key为id属性值,value为对应的class属性值创建的对象 */
private Map<String, Object> beans = new HashMap<String, Object>();
/** 存放自动扫描包的配置信息,<scan package="xx"/> */
private List<String> packagePaths = new ArrayList<String>();
2),读取配置文件,通过dom4j,所以必需要导入dom4j的相关jar包
/**
* 通过dom4j读取配置信息,将读取的配置信息存放到beanDefines集合中
* @param fileName
*/
@SuppressWarnings("unchecked")
private void readXml(String fileName) {
SAXReader sReader = new SAXReader();
URL url = BeanFactory.class.getClassLoader().getResource(fileName);
Document document = null;
try {
document = sReader.read(url);
Map<String, String> map = new HashMap<String, String>();
map.put("ns", "www.zcl.com.cn");
XPath xPath = document.createXPath("//ns:beans/ns:bean");
xPath.setNamespaceURIs(map);//设置名字空间
List<Element> elements = xPath.selectNodes(document);
for (Element element : elements) {
String id = element.attributeValue("id");
String className = element.attributeValue("class");
if (id != null && !id.trim().equals("") && className != null && !className.trim().equals("")) {
BeanDefinition beanDefine = new BeanDefinition(id, className);
xPath = element.createXPath("ns:property");
xPath.setNamespaceURIs(map);
List<Element> propertyList = xPath.selectNodes(element);
for (Element prop : propertyList) {
String name = prop.attributeValue("name");
String ref = prop.attributeValue("ref");
if (name != null && !name.trim().equals("") && ref != null && !ref.trim().equals("")) {
BeanProperty beanProperty = new BeanProperty(name, ref);
beanDefine.addProps(beanProperty);
}
}
beanDefines.add(beanDefine);
}
}
xPath = document.createXPath("//ns:beans/ns:scan");
xPath.setNamespaceURIs(map);
elements = xPath.selectNodes(document);
for (Element e : elements) {
String packageName = e.attributeValue("package");
packagePaths.add(packageName);
}
} catch (DocumentException e) {
log.error("解析xml文件失败!");
throw new RuntimeException(e);
}
}
上面的代码将读取指定xml的文件,会将<property name="" ref=""/>的内容存放在BeanProperty对象中,会将
<bean id="" class="">的信息存放在BeanDefinition中,最后再放到成员变量beanDefines中。
3),通过xml初始化读取到beanDefines的对象
/**
* beanDefines集合中的信息初始化bean并存放到Map中
*/
private void initBeansByXml() {
for (BeanDefinition beanDefine : beanDefines) {
try {
/*
*将id作为key值,初始化的bean作为value存放到Map中
*/
beans.put(beanDefine.getId(), Class.forName(beanDefine.getClassName()).newInstance());
} catch (Exception e) {
log.error("初始化bean失败");
throw new RuntimeException(e);
}
}
}
4),通过注解初始化Bean
/**
* 通过注解初始化Bean
*/
private void initBeanByAnnotation() {
//得到经过utf-8编码的classpath路径
URL url = BeanFactoryTest.class.getClassLoader().getResource("");
String rootPath = null;
try {
//解码成标准形式的路径格式
rootPath = java.net.URLDecoder.decode(url.getPath(),"UTF-8");
} catch (UnsupportedEncodingException e) {
log.error("解析项目路径时错误!");
throw new RuntimeException(e);
}
//遍历每个配置了<scan package=""/>的信息
for (String packagePath : packagePaths) {
File dir = new File(rootPath, saxReader(packagePath));
if (dir == null || !dir.isDirectory()) { //如果没有设置包,则出异常
log.error("配置的package不是目录或不存在!");
throw new RuntimeException("配置的package不是目录或不存在!");
}
handler(packagePath, dir);
}
}
5),通过xml注入各种bean
/**
* 通过xml注入各种bean
* 1,先遍历在xml文件中配置的所有的bean信息
* 2,然后通过id的值从map中得到当前遍历的Bean对象
* 3,再遍历当前bean对象中所有的属性
* 4,判断对象的属性名是否在配置文件中配置过
* 5,如果配置了则通过配置文件中的ref的值从map中取出对应的bean
* 6,通过setter()方法设置到bean中完成注入
*/
private void injectByXml() {
for (BeanDefinition beanDefine : beanDefines) { //遍历配置文件中所有的bean信息
Object obj = beans.get(beanDefine.getId()); //得到bean实体
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptor = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor desc : propertyDescriptor) { //遍历bean实体里所有的属性
for (BeanProperty beanProperty : beanDefine.getProps()) {
//如果属性名与配置文件中的<property name="" ref/>的name的属性值相等,则注入
if (beanProperty.getName().equals(desc.getName())) {
Method method = desc.getWriteMethod();//得到Setter()方法
method.setAccessible(true);//暴力破解,防止用户将setter方法丢了public后,程序无法注入
Object val = beans.get(beanProperty.getRef());
if (val == null) {
log.error("找不到【" + beanProperty.getName() + "】对应的bean对象");
throw new RuntimeException("找不到【" + beanProperty.getName() + "】对应的bean对象");
}
method.invoke(obj, val);//注入
}
}
}
} catch (IntrospectionException e) {
log.error("得到beanInfo时发生异常");
throw new RuntimeException(e);
} catch (Exception e) {
log.error("调用invoke()方法时异常");
throw new RuntimeException(e);
}
}
}
6),通过注解注入各种bean
/**
* 通过注解注入各种bean
* 1,先检查bean的Setter()方法上有无Resource注解
* 2,再检查属性字段上有无Resource注解
* 在注入时:
* 1,先通过Resource(name="")的name注入,若没有设置name则使用属性名
* 2,若没匹配的属性名,则通过类型注入
*/
private void injectByAnnotation() {
for (Entry<String, Object> entry : beans.entrySet()) { //遍历每个bean对象
Object obj = entry.getValue();
try {
//先检查setter()方法上有无设置Resource注解
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptor = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor desc : propertyDescriptor) { //遍历每个属性
Method method = desc.getWriteMethod();//得到Setter()方法
//如果setter()方法上标识有Resource注解
if (method != null && method.isAnnotationPresent(Resource.class)) {
Resource resource = method.getAnnotation(Resource.class);//得到此注解
String name = resource.name(); //得到name
if (name == null || name.trim().equals("")) {
name = desc.getName();//如果没有使用name,形如:@Resource, 则将属性的名字作为name
}
Object val = beans.get(name); //从Map中得到此bean对象
if (val == null) { //若为空,则通过类型注入
for (Object o : beans.values()) { //再次遍历Bean
//如果需要注入的bean是Map中某个bean的类型相同或者是其超类,则注入
if (desc.getPropertyType().isAssignableFrom(o.getClass())) {
val = o;
break;
}
}
}
method.invoke(obj, val);
}
}
//通过配置在属性字段上的Resource注入
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
//检查属性字段上有无Resource注解
if (field.isAnnotationPresent(Resource.class)) {
Resource resource = field.getAnnotation(Resource.class);
String name = resource.name();
if (name == null) {
name = field.getName();
}
Object val = beans.get(name);
if (val == null) { //通过属性名注解时找不到匹配的bean,则通过类型注入
for (Object o : beans.values()) {
if (field.getType().isAssignableFrom(o.getClass())) {
val = o;
break;
}
}
}
field.setAccessible(true);
field.set(obj, val);
}
}
} catch (IntrospectionException e) {
log.error("得到beanInfo时发生异常");
e.printStackTrace();
} catch (Exception e) {
log.error("调用invoke()方法时异常");
e.printStackTrace();
}
}
}
7,编写getBean()方法得到容器中的bean
/**
* 从环境中得到Bean对象
* @param <T>
* @param id
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getBean(String id, Class<T> clazz) {
return (T) beans.get(id);
}
/**
* 得到代理的对象
* @param <T>
* @param id
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getBeanProxy(String id, Class<T> clazz) {
final T realObj = getBean(id, clazz);
return (T)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(realObj, args);
}
});
}
8, 最后通过构造方法来调用各个方法
/**
* 创建此对象时会依次执行下列方法
* */
public BeanFactory() {
readXml("applicationContext.xml"); //读取配置文件
initBeansByXml(); //通过xml配置文件初始化各种对象
initBeanByAnnotation(); //通过annotation初始化各种对象
injectByXml(); //通过xml注入各种对象
injectByAnnotation(); //通过annatation注入各种对象
}
再写个测试程序:
@Test
public void testBeanFactory() {
BeanFactory factory = new BeanFactory();
UserService userService = factory.getBean("userService", UserServiceImpl.class);
userService.add();
}
用法有两种方式:
xml方式:
完全和spring的配置一样
Annotation方式:
可以在配置文件中写上<scan package="xx"/>,其中xx表示根路径下的一个包,程序可以通过此配置的xx遍历基下所有的标注有@Service注解的类,并实例化。在使用Service注解时可以带上Service("userService"),若不指定默认会使用类名作为标识。
在实现注入对象的功能只需在要注入的对象的属性字段或者setter()方法标注@Resource,当然也可以这样:
@Resourc(name="xx"),当不指定xx时会以属性字段的名称去查找容器中的bean对象,若没有匹配的,则按类型匹配,其实这和spring实现的一样。
最后通过getBean()【得到真实的对象】或getBeanProxy()【得到代理对象】来找某个bean.注意通过getBeanProxyt得到的对象必须用接口去接收(我想大家也知道原因吧)
本人是第一次正经地发贴,若有什么不好的或值得改进的,请大家指点。谢谢。
注:附件是源代码(包括jar包)。