利用spring,实现package下的类扫描
项目中需要用到包扫描的情况是很多的,一般是在项目初始化的时候,根据一些条件来对某个package下的类进行特殊处理。现在想实现的功能是,在一个filter或interceptor初始化的时候,扫描指定的一些package路径,遍历下面的每个class,找出method上使用了一个特殊注解的所有方法,然后缓存起来,当方法拦截器工作的时候,就不用再挨个判断方法是否需要拦截了
网上有很多自己编码实现scan package功能的例子,但是如果有工具已经帮你实现了,并且经受了普遍的验证,那么,自己造轮子的必要性就不大了
spring框架中有扫描包的类ClassPathBeanDefinitionScanner 里面的findCandidateComponents方法是我们进行改造的依据
/**
* Scan the class path for candidate components.
* @param basePackage the package to check for annotated classes
* @return a corresponding Set of autodetected bean definitions
*/
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
改造如下:
方法loadCheckClassMethods的入参是逗号分隔的包路径,如com.xx
利用Spring的
ResourcePatternResolver来寻找包下面的资源Resource,因为我们的扫描pattern是.class文件,所以这里的Resource就是class文件
protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
/**
* 根据扫描包的配置
* 加载需要检查的方法
*/
private void loadCheckClassMethods(String scanPackages) {
String[] scanPackageArr = scanPackages.split(",");
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
for (String basePackage : scanPackageArr) {
if (StringUtils.isBlank(basePackage)) {
continue;
}
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN;
try {
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
//检查resource,这里的resource都是class
loadClassMethod(metadataReaderFactory, resource);
}
} catch (Exception e) {
log.error("初始化SensitiveWordInterceptor失败", e);
} }
}
/**
* 加载资源,判断里面的方法
*
* @param metadataReaderFactory spring中用来读取resource为class的工具
* @param resource 这里的资源就是一个Class
* @throws IOException
*/
private void loadClassMethod(MetadataReaderFactory metadataReaderFactory, Resource resource) throws IOException {
try {
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
if (metadataReader != null) {
String className = metadataReader.getClassMetadata().getClassName();
try {
tryCacheMethod(className);
} catch (ClassNotFoundException e) {
log.error("检查" + className + "是否含有需要信息失败", e);
}
}
}
} catch (Exception e) {
log.error("判断类中的方法实现需要检测xxx失败", e);
}
}
/**
* 把action下面的所有method遍历一次,标记他们是否需要进行xxx验证
* 如果需要,放入cache中
*
* @param fullClassName
*/
private void tryCacheMethod(String fullClassName) throws ClassNotFoundException {
Class<?> clz = Class.forName(fullClassName);
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
if (method.getModifiers() != Modifier.PUBLIC) {
continue;
}
if (CheckXXX.class.isAssignableFrom(CHECK_ANNOTATION)) {
CheckXXX checkXXX = (CheckXXX) method.getAnnotation(CHECK_ANNOTATION);
if (checkXXX != null && checkXXX.check()) {
cache.put(fullClassName + "." + method.getName(), checkXXX);
log.info("检测到需要检查xxx的方法:" + fullClassName + "." + method.getName());
}
}
}
}
tryCacheMethod做的事就是缓存需要处理的public方法
经测试,这种方式可以取到web的class文件和jar包中的class文件
加强版,package父子集合判断
package utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.SystemPropertyUtils; import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set; /**
* Created by cdliujian1 on 2016/1/13.
* 包工具,根据package路径,加载class
*/
public class PackageUtil { private final static Log log = LogFactory.getLog(PackageUtil.class);
//扫描 scanPackages 下的文件的匹配符
protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; /**
* 结合spring的类扫描方式
* 根据需要扫描的包路径及相应的注解,获取最终测method集合
* 仅返回public方法,如果方法是非public类型的,不会被返回
* 可以扫描工程下的class文件及jar中的class文件
*
* @param scanPackages
* @param annotation
* @return
*/
public static Set<Method> findClassAnnotationMethods(String scanPackages, Class<? extends Annotation> annotation) {
//获取所有的类
Set<String> clazzSet = findPackageClass(scanPackages);
Set<Method> methods = new HashSet<Method>();
//遍历类,查询相应的annotation方法
for (String clazz : clazzSet) {
try {
Set<Method> ms = findAnnotationMethods(clazz, annotation);
if (ms != null) {
methods.addAll(ms);
}
} catch (ClassNotFoundException ignore) {
}
}
return methods;
} /**
* 根据扫描包的,查询下面的所有类
*
* @param scanPackages 扫描的package路径
* @return
*/
public static Set<String> findPackageClass(String scanPackages) {
if (StringUtils.isBlank(scanPackages)) {
return Collections.EMPTY_SET;
}
//验证及排重包路径,避免父子路径多次扫描
Set<String> packages = checkPackage(scanPackages);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
Set<String> clazzSet = new HashSet<String>();
for (String basePackage : packages) {
if (StringUtils.isBlank(basePackage)) {
continue;
}
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
org.springframework.util.ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN;
try {
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
//检查resource,这里的resource都是class
String clazz = loadClassName(metadataReaderFactory, resource);
clazzSet.add(clazz);
}
} catch (Exception e) {
log.error("获取包下面的类信息失败,package:" + basePackage, e);
} }
return clazzSet;
} /**
* 排重、检测package父子关系,避免多次扫描
*
* @param scanPackages
* @return 返回检查后有效的路径集合
*/
private static Set<String> checkPackage(String scanPackages) {
if (StringUtils.isBlank(scanPackages)) {
return Collections.EMPTY_SET;
}
Set<String> packages = new HashSet<String>();
//排重路径
Collections.addAll(packages, scanPackages.split(","));
for (String pInArr : packages.toArray(new String[packages.size()])) {
if (StringUtils.isBlank(pInArr) || pInArr.equals(".") || pInArr.startsWith(".")) {
continue;
}
if (pInArr.endsWith(".")) {
pInArr = pInArr.substring(0, pInArr.length() - 1);
}
Iterator<String> packageIte = packages.iterator();
boolean needAdd = true;
while (packageIte.hasNext()) {
String pack = packageIte.next();
if (pInArr.startsWith(pack + ".")) {
//如果待加入的路径是已经加入的pack的子集,不加入
needAdd = false;
} else if (pack.startsWith(pInArr + ".")) {
//如果待加入的路径是已经加入的pack的父集,删除已加入的pack
packageIte.remove();
}
}
if (needAdd) {
packages.add(pInArr);
}
}
return packages;
} /**
* 加载资源,根据resource获取className
*
* @param metadataReaderFactory spring中用来读取resource为class的工具
* @param resource 这里的资源就是一个Class
* @throws IOException
*/
private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource) throws IOException {
try {
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
if (metadataReader != null) {
return metadataReader.getClassMetadata().getClassName();
}
}
} catch (Exception e) {
log.error("根据resource获取类名称失败", e);
}
return null;
} /**
* 把action下面的所有method遍历一次,标记他们是否需要进行敏感词验证
* 如果需要,放入cache中
*
* @param fullClassName
*/
public static Set<Method> findAnnotationMethods(String fullClassName, Class<? extends Annotation> anno) throws ClassNotFoundException {
Set<Method> methodSet = new HashSet<Method>();
Class<?> clz = Class.forName(fullClassName);
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
if (method.getModifiers() != Modifier.PUBLIC) {
continue;
}
Annotation annotation = method.getAnnotation(anno);
if (annotation != null) {
methodSet.add(method);
}
}
return methodSet;
} public static void main(String[] args) {
String packages = "com.a,com.ab,com.c,com.as.t,com.as,com.as.ta,com.at.ja,com.at.jc,com.at.";
System.out.println("检测前的package: " + packages);
System.out.println("检测后的package: " + StringUtils.join(checkPackage(packages), ","));
} }
利用spring,实现package下的类扫描的更多相关文章
- 利用Spring.Net技术打造可切换的分布式缓存读写类
利用Spring.Net技术打造可切换的Memcached分布式缓存读写类 Memcached是一个高性能的分布式内存对象缓存系统,因为工作在内存,读写速率比数据库高的不是一般的多,和Radis一样具 ...
- spring类扫描注入-----类扫描的注解解析器
通过类扫描注入到容器中,这种方式,在实际开发中还是很常用的,可以看下自己的配置文件,就会发现,自己公司的项目,搞不好就是这么注入的. 起码,我发现我公司的项目就是这么干的. 下面来演示一下简单的例子: ...
- Spring Boot@Component注解下的类无法@Autowired的问题
title: Spring Boot@Component注解下的类无法@Autowired的问题 date: 2019-06-26 08:30:03 categories: Spring Boot t ...
- Java学习笔记5---命令行下用javac,java编译运行含package语句的类
对于笔记3中的HelloWorld程序,编译时只要输入javac HelloWorld.java即可生成类文件:再用java HelloWorld即可运行. 如果源程序使用了包声明,编译运行时要使用某 ...
- spring boot利用controller来测试写的类
我们在开发spring boot应用程序的时候,往往需要测试某个写好的类,但是在测试的时候发现不太好测试,用Junit等测试框架,总是会报一些问题,大致是找不到配置文件以及无法利用spring创建的对 ...
- spring启动component-scan类扫描加载,以及@Resource,postConstruct等等注解的解析生效源码
spring里IOC的原理就不详细写了, 如果想要搞清楚自动扫描组件是如何实现的,还有@Resouce @PostConstruct等注解的工作原理,最好可以先搞清楚整个IOC容器的运作原理再来分析这 ...
- Spring boot 梳理 - 全局配置文件application.properties或者是application.yml,在resources目录下或者类路径下的/config下,一般我们放到resources下。
全局配置文件application.properties或者是application.yml,在resources目录下或者类路径下的/config下,一般我们放到resources下.
- Spring实战6:利用Spring和JDBC访问数据库
主要内容 定义Spring的数据访问支持 配置数据库资源 使用Spring提供的JDBC模板 写在前面:经过上一篇文章的学习,我们掌握了如何写web应用的控制器层,不过由于只定义了SpitterRep ...
- 你知道Spring是怎么解析配置类的吗?
彻底读懂Spring(二)你知道Spring是怎么解析配置类的吗? 推荐阅读: Spring官网阅读系列 彻底读懂Spring(一)读源码,我们可以从第一行读起 Spring执行流程图如下: 如果图片 ...
随机推荐
- virualbox 安装 otter 必备软件
前言 最近研究了一下阿里otter项目(分布式数据库同步),所以就在virualbox 上开始准备学习一下,遇到了不少坑,所以记录一下啊. otter 项目:https://github.com/al ...
- 利用VS2008发布一个简单的webservice
一个开发好的webservice,怎样发布出去,供其他电脑访问呢? 本文将介绍如何发布一个简单的webservice,其中的内容都是在网上查看别人文章,自己仿照着做了一遍,因此,难免会发生错误,如果发 ...
- 哈尔滨理工大学第六届程序设计团队 I-Team
/* 以前做过一个插队的题,这个类似从后往前操作 */ #include <iostream> #include <stdio.h> #include <algorith ...
- Log4j – Log4j 2 API
Overview The Log4j 2 API provides the interface that applications should code to and provides the ad ...
- SQL Server 数据类型转换函数
T-SQL提供了两个显示转换的函数:CAST函数和CONVERT函数. 1. CAST函数 语法: CAST ( expression AS data_type [ ( length ) ] ) 示例 ...
- 安全框架Shiro入门
Shiro简介 Apache Shiro是Java的一个安全框架,官网为shiro.apache.org,主要场景为控制登陆,判断用户是否有访问某个功能的权限等等. Shiro的核心功能(入门知识,只 ...
- 一款非常推荐的用户界面插件----EasyUI
前 言 easyui是一种基于jQuery的用户界面插件集合. easyui为创建现代化,互动,JavaScript应用程序,提供必要的功能. 使用easyui你不需要写很多代码,你只需要 ...
- 高阶函数实现AOP
AOP(面向切面程序)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日至统计.安全控制.异常处理等.把这些功能抽离出来之后,再通过"动态织入&quo ...
- Windows定时器学习
定时器是一个在特定时间或者规则间隔被激发的内核对象.结合定时器的异步程序调用可以允许回调函数在任何定时器被激发的时候执行. 通过调用CreateWaitableTimer()可以创建一个定时器,此函数 ...
- CLR类型设计之属性
在之前的随笔中,我们探讨了参数,字段,方法,我们在开始属性之前回顾一下,之前的探讨实际上串联起来就是OOP编程的思想,在接下来的文章中,我们还会讨论接口(就是行为),举个例子:我们如果要做一个学生档案 ...