首先需要解释下,何谓Bean动态注册?我们知道,我们A类依赖B类,且A、B类都需要在Spring的applicationContext.xml中进行注册,A、B的依赖关系通过property的ref属性映射,还有一种实现方式就是使用注解,Spring IOC内置了很多有关bean组件的注解,比如@Component、@Resource、@Service,但是这些都是需要我们手动去一个一个的去配置,不管是配置XML还是配置注解,我都觉得很麻烦,所以最近几天我都一直在思考,能不能实现我们的javabean不用显式的在applicationContext.xml里注册并且不用添加spring bean相关的注解,在程序运行时动态的把某些bean临时的交给spring IOC 容器来管理,这就是困扰我几天的问题,幸好还是解决了,决定有必要记录一下。我们知道spring的contextLoadListener是在工程启动时去加载spring配置文件的,在运行时修改applicationContext.xml必须重新加载该配置文件才能让spring重新解析xml配置,然后刷新ApplicationContext即可实现bean的动态注册。在一遍一遍翻阅了Spring的相关源码后,开始有点思路了,今晚回来就尝试写了个示例,没想到果然成功了,greate!其实实现一点都不难,主要是你要熟悉spring的API,能从spring的源码中洞穿框架设计者的本意,这才是解决问题的根本所在。
下面贴出一些核心代码,其实也没多少代码,主要是实现这种功能是我这个人比较爱偷懒,不想每个bean写注解.
我随便写了一个TestService接口及其实现类,且引用了TestDao,但是两者都没在spring的配置文件中注册,即它们暂时是和spring IOC容器没有任何关系的,他们现在就是两个再简单不过的javaBean,
TestDao接口和它的实现类也没什么,因为只是测试,所以图简单,代码如下:
然后贴下applicationContext.xml的代码,其实这个xml里什么都没配置,
注意:
我的Service和dao既没在applicationContext.xml中注册也没加注解哦,亲! 下面到重点了,写个测试类,看看到底该这样在程序运行时动态的把bean注册进spring容器,然后就跟事先在applicationContext.xml中注册过该bean一样,以前怎么从spring容器中拿bean实例,现在就怎么拿,对于用户来说,bean是静态注册的还是动态注册的,毫无知觉的。
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.spel.ast.Identifier;
import org.springframework.util.ClassUtils;
import com.nicolas.service.TestService;
import com.nicolas.service.impl.TestServiceImpl;
import com.sun.org.apache.bcel.internal.util.ClassPath;
public class Test2 {
public static void main(String[] args) throws BeanDefinitionStoreException, ClassNotFoundException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "applicationContext.xml");
//ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)context.getBeanFactory();
//TestService testService = new TestServiceImpl();
//这里是动态注册单例,不过实例要事先自己new,这种做法不推荐
//beanFactory.registerSingleton("testService", testService);
//这里是动态注册非单例bean,把setScope("prototype") 改为setScope("singleton")就是单例注册了,推荐使用这种方式动态注册
//有的同学可能会问,采用动态注册Bean有上面好处,好处就是:
//你的bean再也不需要在Spring的applicationContext.xml中注册了,也不需要在你的bean上加什么@Component、@Resource、@Service等各种bean相关的注解了
//从bean注册中彻底解放了,你甚至可以自己写一个监听器(比如实现一个ServletContextListener),在项目启动时,搜索项目中需要注册的bean通过现在这种方法动态注册,后来新增的bean可以额外再动态注册,是不是感觉世界又美好了很多
beanFactory.registerBeanDefinition("testDao", BeanDefinitionBuilder.genericBeanDefinition(Class.forName("com.nicolas.dao.impl.TestDaoImpl")).setScope("prototype").getRawBeanDefinition());
beanFactory.registerBeanDefinition("testService", BeanDefinitionBuilder.genericBeanDefinition(Class.forName("com.nicolas.service.impl.TestServiceImpl"))
.setScope("prototype").addPropertyReference("testDao", "testDao").getRawBeanDefinition());
TestService testService2 = (TestService)beanFactory.getBean("testService");
TestService testService3 = (TestService)beanFactory.getBean("testService");
//判断是否为单例,false就表明不是单例
//com.nicolas.service.impl.TestServiceImpl这里的包路径你其实可以通过项目某个包下的所有以某某字符串结尾的类就可以获取得到
//比如你搜索项目中com.yida.service.impl包下的所有以Impl结尾的类,你得到了类,那通过类就可以自动类的名称,再拼接上包路径,那么
//那上面写死的包路径你就可以动态获取了,这里仅仅只是为了测试
System.out.println(testService2.equals(testService3));
testService2.sayHello();
}
上面的代码中注释已经说的很清楚了,就不再细说了。我这么折腾到底能带来什么好处?我想你肯定在心底会这么问,这么折腾还不是为了偷懒,不想写配置文件,不想记各种注解,蛋疼有木有?写多了自然就会有:能不能自动配置或者自动注册诸如之类的想法,不会偷懒的程序员算不上好程序员。OK,解释完毕!我今晚也折腾完毕!感谢大家一如既往对我博客的关注,我自我感觉文笔不怎么样,很少出来献丑。偶尔是有点忍不住,就瞎写写,大家勉强凑合凑合看吧,觉得对你有帮助我就很欣慰了,觉得写的不好请正面指正,谢谢!闲话不扯了,最后还是要为我的java群打个广告,欢迎广大java爱好者一起进行java学习交流,群号:205937275!
Sorry,上面提到说搜索某个包下的所有类,但是没贴出来关键代码,以免大家说我光说不练忽悠人,还是决定把该实现代码贴出来,其实也不是我写的,是网上google来的,我觉得有价值,自己也测试通过了,就分享给大家了!
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
public class ClassPathScanHandler {
private static final Logger logger = Logger
.getLogger(ClassPathScanHandler.class);
private boolean excludeInner = true;
private boolean checkInOrEx = true;
private List<String> classFilters = null;
public ClassPathScanHandler() {
}
public ClassPathScanHandler(Boolean excludeInner, Boolean checkInOrEx,
List<String> classFilters) {
this.excludeInner = excludeInner;
this.checkInOrEx = checkInOrEx;
this.classFilters = classFilters;
}
public Set<Class<?>> getPackageAllClasses(String basePackage,
boolean recursive) {
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
String packageName = basePackage;
if (packageName.endsWith(".")) {
packageName = packageName
.substring(0, packageName.lastIndexOf('.'));
}
String package2Path = packageName.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(
package2Path);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
logger.info("扫描file类型的class文件....");
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
doScanPackageClassesByFile(classes, packageName, filePath,
recursive);
} else if ("jar".equals(protocol)) {
logger.info("扫描jar文件中的类....");
doScanPackageClassesByJar(packageName, url, recursive,
classes);
}
}
} catch (IOException e) {
logger.error("IOException error:", e);
}
return classes;
}
private void doScanPackageClassesByJar(String basePackage, URL url,
final boolean recursive, Set<Class<?>> classes) {
String packageName = basePackage;
String package2Path = packageName.replace('.', '/');
JarFile jar;
try {
jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (!name.startsWith(package2Path) || entry.isDirectory()) {
continue;
}
// 判断是否递归搜索子包
if (!recursive
&& name.lastIndexOf('/') != package2Path.length()) {
continue;
}
// 判断是否过滤 inner class
if (this.excludeInner && name.indexOf('$') != -1) {
logger.info("exclude inner class with name:" + name);
continue;
}
String classSimpleName = name
.substring(name.lastIndexOf('/') + 1);
// 判定是否符合过滤条件
if (this.filterClassName(classSimpleName)) {
String className = name.replace('/', '.');
className = className.substring(0, className.length() - 6);
try {
classes.add(Thread.currentThread()
.getContextClassLoader().loadClass(className));
} catch (ClassNotFoundException e) {
logger.error("Class.forName error:", e);
}
}
}
} catch (IOException e) {
logger.error("IOException error:", e);
}
}
private void doScanPackageClassesByFile(Set<Class<?>> classes,
String packageName, String packagePath, boolean recursive) {
File dir = new File(packagePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
final boolean fileRecursive = recursive;
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义文件过滤规则
public boolean accept(File file) {
if (file.isDirectory()) {
return fileRecursive;
}
String filename = file.getName();
if (excludeInner && filename.indexOf('$') != -1) {
logger.info("exclude inner class with name:" + filename);
return false;
}
return filterClassName(filename);
}
});
for (File file : dirfiles) {
if (file.isDirectory()) {
doScanPackageClassesByFile(classes, packageName + "."
+ file.getName(), file.getAbsolutePath(), recursive);
} else {
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
classes.add(Thread.currentThread().getContextClassLoader()
.loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
logger.error("IOException error:", e);
}
}
}
}
private boolean filterClassName(String className) {
if (!className.endsWith(".class")) {
return false;
}
if (null == this.classFilters || this.classFilters.isEmpty()) {
return true;
}
String tmpName = className.substring(0, className.length() - 6);
boolean flag = false;
for (String str : classFilters) {
String tmpreg = "^" + str.replace("*", ".*") + "$";
Pattern p = Pattern.compile(tmpreg);
if (p.matcher(tmpName).find()) {
flag = true;
break;
}
}
return (checkInOrEx && flag) || (!checkInOrEx && !flag);
}
public boolean isExcludeInner() {
return excludeInner;
}
public boolean isCheckInOrEx() {
return checkInOrEx;
}
public List<String> getClassFilters() {
return classFilters;
}
public void setExcludeInner(boolean pExcludeInner) {
excludeInner = pExcludeInner;
}
public void setCheckInOrEx(boolean pCheckInOrEx) {
checkInOrEx = pCheckInOrEx;
}
public void setClassFilters(List<String> pClassFilters) {
classFilters = pClassFilters;
}
public static void main(String[] args) {
ClassPathScanHandler handler = new ClassPathScanHandler();
Set<Class<?>> classSet = handler.getPackageAllClasses(
"com.nicolas.service.impl", true);
for (Iterator iterator = classSet.iterator(); iterator.hasNext();) {
Class clazz = (Class) iterator.next();
System.out.println(clazz.getName());
}
}
}