在spring中玩转自定义注解
- 1.注解扫描生效原理
- 2.基于@Component元注解开发自定义注解
- 2.1 开发注解,使用component作为元注解
- 2.2 实现InstantiationAwareBeanPostProcessor接口,实现自定义创建对象进行注册
- 3.基于注解扫描开发自定义注解
- 3.1开启功能的自定义注解,标记在启动类上
- 3.2 开发自定义注解,注解在需要注册的类上
- 3.3 注册类开发
- 3.4 FactoryBean开发
- 3.5 处理器开发和数据封装
- 3.6 注解的使用
本文教你如何在spring中自定义注解,并像spring原生注解一样启动就生效。
1.注解扫描生效原理
ClassPathScanningCandidateComponentProvider类:这个类的主要作用是进行包扫描,可以指定扫描的包和特定的类,默认扫描路径是类路径,且扫描的是Component注解标记的类或者Componet作为元注解标记的注解标记的类。这个类有两个变量 includeFilters 和 excludeFilters ,前者用来表示扫描中需要过滤包含的条件,后者用来标识扫描过程中过滤排除的条件,且判断逻辑是先判断excludeFilters 排除条件 ,后判断 includeFilters 条件,前面的条件生效则不再判断后面的条件。
ClassPathBeanDefinitionScanner是上一个类的父类,在其基础上做了注册功能,所以ClassPathBeanDefinitionScanner需要传入一个BeanDefinitionRegistry对象.
在 Spring 中可以通过 MetadataReader 获取 ClassMetadata 以及 AnnotationMetadata,然后获取相应元数据。ClassMetadata 可以获取类的各种元数据,比如类名,接口等。
而 AnnotationMetadata 可以获取当前类上注解的元数据,如注解名字,以及元注解信息等。
AnnotationMetadata 存在两个实现类分别为 StandardAnnotationMetadata与 AnnotationMetadataReadingVisitor。
StandardAnnotationMetadata主要使用 Java 反射原理获取元数据。
AnnotationMetadataReadingVisitor 使用 ASM 框架获取元数据。
2.基于@Component元注解开发自定义注解
2.1 开发注解,使用component作为元注解
@Component
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface GBaseMapper {
String sql();
int timeout() default 30 ;
}
2.2 实现InstantiationAwareBeanPostProcessor接口,实现自定义创建对象进行注册
@Component
public class GbaseInstatiationBeanPostProcess implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
//获取自定义注解,判断是否是自定义注解标记的类
GBaseMapper annotation = beanClass.getAnnotation(GBaseMapper.class);
//非自定义注解标记类则不处理
if (annotation == null)
return null;
Class<?>[] interfaces = beanClass.getInterfaces();
Object source = null;
try {
//创建对象
source = beanClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//将对象进行动态代理,返回注册代理对象
Object pro = Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces, new MyHandler(source));
return pro;
}
private class MyHandler implements InvocationHandler{
//被代理对象
private Object target;
//缓存被代理对象中方法与注解的映射
private ConcurrentHashMap<Method,GBaseMapper> map = new ConcurrentHashMap<Method,GBaseMapper>();
public MyHandler(Object target) {
this.target = target;
Method[] methods = target.getClass().getMethods();
for (int i = 0 ; i < methods.length;i++){
if (methods[i].getAnnotation(GBaseMapper.class) != null) {
map.putIfAbsent(methods[i], methods[i].getAnnotation(GBaseMapper.class));
}
}
}
//被代理逻辑
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//获取被代理对象类上的注解的sql语句
String sql = target.getClass().getAnnotation(GBaseMapper.class).sql();
//获取被代理对象中执行的方法对象,参数中传进来的是可能是接口的,被代理对象是实现类
Method targetMethoed = target.getClass().getMethod(method.getName(), method.getParameterTypes());
GBaseMapper annotation = map.get(targetMethoed);
System.out.println("==="+(annotation == null? sql : annotation.sql()));
return method.invoke(target,objects);
}
}
}
3.基于注解扫描开发自定义注解
3.1开启功能的自定义注解,标记在启动类上
注意这个类导入了ProxyRequestRegistrar这个类,这个类负责自定义注解 ProxyHttp的扫描和类的注入
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(ProxyRequestRegistrar.class)
public @interface EnableHttpRequest {
}
3.2 开发自定义注解,注解在需要注册的类上
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ProxyHttp {
String url() ;
int timeout() default 30 ;
Class callback() default void.class;
}
3.3 注册类开发
负责注解的扫描和自定义类的加载
package com.heron.myproxy;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import java.util.Map;
import java.util.Set;
/**
* @Author HeRong
* @Description 注册bean
* @Date 2020/9/3 10:49
* @Version V1.0
*/
public class ProxyRequestRegistrar implements ImportBeanDefinitionRegistrar , ResourceLoaderAware , EnvironmentAware {
private ResourceLoader loader;
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment ;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.loader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathScanningCandidateComponentProvider scaner = getScaner();
scaner.setResourceLoader(loader);
//添加注解过滤器,只扫描ProxyHttp注解的类
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(ProxyHttp.class);
scaner.addIncludeFilter(annotationTypeFilter);
//默认扫描主类下的包
String classPathPackage = ClassUtils.getPackageName(annotationMetadata.getClassName());
Set<BeanDefinition> candidateComponets = scaner.findCandidateComponents(classPathPackage);
for (BeanDefinition component : candidateComponets){
if (component instanceof AnnotatedBeanDefinition){
//扫描到的注解标记的接口的定义信息
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)component;
AnnotationMetadata metadata = beanDefinition.getMetadata();
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ProxyHttp.class.getCanonicalName());
Assert.isTrue(metadata.isInterface(),"@ProxyHttp注解只能作用在接口上");
//构造 HttpReqFactoryBean的定义信息
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HttpReqFactoryBean.class);
builder.addPropertyValue("type",metadata.getClassName());
builder.addPropertyValue("url",annotationAttributes.get("url"));
builder.addPropertyValue("timeout",annotationAttributes.get("timeout"));
builder.addPropertyValue("callback",annotationAttributes.get("callback"));
// metadata.getAnnotatedMethods()
//注册HttpReqFactoryBean
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(builder.getBeanDefinition(), metadata.getClassName());
BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder,beanDefinitionRegistry);
}
}
}
private ClassPathScanningCandidateComponentProvider getScaner(){
return new ClassPathScanningCandidateComponentProvider(false,this.environment){
//重写判断是否所需的候选组件的方法
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//拿到元数据,判断是否能够被独立的创建
if (beanDefinition.getMetadata().isIndependent()) {
//判断是否是接口,自定义注解只作用在接口上
if (beanDefinition.getMetadata().isInterface()) {
return true;
}
}
return false;
}
};
}
}
3.4 FactoryBean开发
负责接口代理类的创建和数据封装传递
public class HttpReqFactoryBean implements FactoryBean<Object> {
private Class<?> type ; //接口类型
private String url;
private int timeout;
private Class callback;
@Override
public Class<?> getObjectType() {
return this.type;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(type.getClassLoader(),new Class[]{type},new HttpReqHolder(url,timeout,callback).newInstance());
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public Class getCallback() {
return callback;
}
public void setCallback(Class callback) {
this.callback = callback;
}
}
3.5 处理器开发和数据封装
public class HttpReqHolder {
private String url;
private int timeout;
private Class callback;
public HttpReqHolder(String url, int timeout, Class callback) {
this.url = url;
this.timeout = timeout;
this.callback = callback;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public Class getCallback() {
return callback;
}
public void setCallback(Class callback) {
this.callback = callback;
}
@Override
public String toString() {
return "HttpReqHolder{" +
"url='" + url + '\'' +
", timeout=" + timeout +
", callback=" + callback +
'}';
}
class MyInvocationHandler implements InvocationHandler{
private HttpReqHolder holder;
public MyInvocationHandler(HttpReqHolder holder) {
this.holder = holder;
}
//代理方法,可以通过多传递RestTemplate来代理http请求
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(holder.toString());
Class<?> returnType = method.getReturnType();
RestfullReq annotation = method.getAnnotation(RestfullReq.class);
String url = annotation.url();
ReqMethod reqMethod = annotation.reqMethod();
return null;
}
}
public InvocationHandler newInstance() {
return new MyInvocationHandler(this);
}
}
3.6 注解的使用
启动类上开启自定义注解使用
@EnableHttpRequest
@SpringBootApplication
@EnableScheduling
public class MemberApplication implements ExitCodeGenerator {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MemberApplication.class, args);
GbaseInterface bean = run.getBean(GbaseInterface.class);
bean.findByName();
// System.out.println(-1<<8);
}
在需要代理的接口上使用自定义注解
@ProxyHttp(url = "http://127.0.0.1:3360/test")
public interface GbaseInterface {
@RestfullReq(url = "/getByName")
void findByName();
void selectAll();
通过自定义注解@ProxyHttp生成接口代理类,代理http发送请求