示例形式概述注解扫描,注解切面组件。
SpringBean注解扫描组件
Spring中bean注解扫描类ClassPathScanningCandidateComponentProvider
, 该类构造参数如下:
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
this(useDefaultFilters, new StandardEnvironment());
}
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
if (useDefaultFilters) {
registerDefaultFilters();
}
this.environment = environment;
}
类实例化方式构造依赖, 参数意义:
useDefaultFilters: 是否走默认的springBean扫描策略, spring启动时该值默认是为true, 扫描的组件是@Component
environment: 环境变量相关,基于spring.profiles相关配置
扩展自定义类
自己扩展的注解需要被SpringBean注解扫描器扫到的话需要注解上增加@Component
,
自定义注解示例
自定义注解,绑定Spring注解@Component
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Component
public @interface VClass {
// 类名称
public String name();
// 表名称
public String tableName();
// 表注释
public String comment() default "";
}
应用自定义注解
在自定义实体类上增加自定义注解
import com.wplus.plugin.htmlplus.anno.bean.VClass;
@VClass(name = "mock", tableName = "tb_mock", comment = "测试实体")
public class Mock {
// this class used for test
}
使用Spring注解扫描器扫描自定义类
/**
* 根据路径扫描带有VClass注解的类
* @param scanPath 要扫描的包路径
* @return
*/
public List scanAnnoBeanName(String scanPath){
if(StringUtils.isBlank(scanPath)){
scanPath = DEFAULT_BEAN_PATH;
}
return scanAnnoBeanName(scanPath, VClass.class);
}
/**
* 根据路径扫描要用来映射的类
* @param scanPath 要扫描的包
* @return
*/
public List scanAnnoBeanName(String scanPath, Class extends Annotation> annotationType){
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(annotationType));
Set sets = provider.findCandidateComponents(scanPath);
if(null !=sets && sets.size() > 0){
List classNames = new ArrayList();
for(BeanDefinition bean: sets){
classNames.add(bean.getBeanClassName());
}
return classNames;
}
return null;
}
public static void main(String[] args) {
List beans = new VanoBeanLoadApiImpl().scanAnnoBeanName("com.wplus.plugin");
System.out.println(beans);
}
程序输出
[com.wplus.plugin.htmlplus.demo.Mock]
SpringResources资源扫描组件
Spring中Resources扫描类GenericApplicationContext
, 对应的资源扫描方法如下:
public Resource getResource(String location) {
return this.resourceLoader != null?this.resourceLoader.getResource(location):super.getResource(location);
}
Resources默认资源加载路径String CLASSPATH_URL_PREFIX = "classpath:";
SpringResources资源组件扩展
定义资源加载方法,增加资源后缀匹配过滤
public List scanResources(String locationPattern, final List accepts) {
if(StringUtils.isBlank(locationPattern)){
locationPattern = DEFAULT_TEMPLATE_PATH;
}
try {
GenericApplicationContext context = new GenericApplicationContext();
Resource rs[] = context.getResources(locationPattern);
List list = (null != rs && rs.length > 0)? Arrays.asList(rs): new ArrayList();
// do return resource list if accepts list is empty.
if(CollectionUtils.isEmpty(accepts)){
return list;
}
// filter resource which file extension in accepts
List result = list.stream() // convert list to stream
.filter(line -> accepts.contains(".".concat(FilenameUtils.getExtension(line.getFilename()))))
.collect(Collectors.toList());
return result;
}catch (Exception e){
LOGGER.error(e.getMessage(), e);
}
return new ArrayList();
}
示例使用Spring资源加载组件,扫描指定路径下的资源模板, 第二个参数用来标识扫描资源结果匹配的过滤,
后缀在接收列表中的资源,会加载到结果集中.
调用示例模拟
public static void main(String[] args) {
List list = new TemplateLoadApiImpl().scanResources("classpath:/HTemplate/**", Arrays.asList(".html"));
list.forEach(li -> System.out.println(li.toString()));
}
方法通过扫描当前工程下资源模块HTemplate
中的内容,递归遍历,收集后缀为.html结尾的资源文件.
如果要扫描jar包中的资源的话,classpath统配符应为"classpath*:/HTemplate/**"
@Aspect说明
Spring除了支持Schema方式配置AOP,还支持使用@Aspect
注解方式切面声明。Spring默认注解支持关闭,开启配置:
这样Spring就能发现@AspectJ风格的切面并且将切面应用到目标对象。
@Aspect依赖包
org.aspectj
aspectjweaver
1.8.9
org.aspectj
aspectjweaver
1.8.9
@Aspect示例
定义切面,切入方法调用耗时
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
@Order(3)
public class TraceInvokeTimelineAroundAspect {
private static final Logger LOG = LoggerFactory.getLogger(TraceInvokeTimelineAroundAspect.class);
public final static long MONITOR_MAX_TIME = 5000;
@Around(
"pointRestful() || pointService() || pointMapper()"
)
public Object invoke(ProceedingJoinPoint point) throws Throwable {
long startTime = System.currentTimeMillis();
String classMethodName = point.getSignature().getDeclaringType().getSimpleName() + "." + point.getSignature().getName();
Object result = point.proceed();
long usedTime = System.currentTimeMillis() - startTime;
StringBuffer buffer = new StringBuffer(600);
buffer.append("耗时:").append(usedTime + "ms").append(" ");
buffer.append("优化(>" + MONITOR_MAX_TIME + "ms):").append(usedTime >= MONITOR_MAX_TIME).append(" ");
buffer.append("接口:").append(classMethodName).append(" ");
buffer.append("入参:").append(Arrays.toString(point.getArgs())).append(" ");
buffer.append("返回:").append(JSON.toJSONString(result));
LOG.info(buffer.toString());
return result;
}
/**
* 声明切入点 - 控制层切入
*/
@Pointcut("execution(* com.tutorial.aspectj.web.controller..*.*(..))")
public void pointRestful() {
}
/**
* 声明切入点 - 业务层切入
*/
@Pointcut("execution(* com.tutorial.aspectj.facade.service..*.*(..))")
public void pointService(){
}
/**
* 声明切入点 - 数据层切入
*/
@Pointcut("execution(* com.tutorial.aspectj.facade.mapper..*.*(..))")
public void pointMapper() {
}
开启@Aspect支持
示例中我们用注解@Component
对自定的Aspect进行了实例化,需要保证注解所在包能被Spring实例注解扫描到。
要使切面在两个容器(spring&springmvc)中都生效,必须两个都必须配置
开启注解支持.
- Spring容器
- SpringMvc容器