实战:基于自定义注解实现自定义框架Spring
一、自定义注解介绍
1.1 通过反射API,可以判断一个类、接口、字段或者方法上是否有注解
Class类(java.lang包下)中提供了一些方法用于反射注解
- //返回指定的注解
getAnnotation - //判断当前元素是否被指定注解修饰
isAnnotationPresent - //返回所有的注解
getAnnotations
例如:判断一个类上是否有注解
在类TransferServiceImpl 上添加自定义注解@MyService
@MyService("myTransferServiceImpl")
public class TransferServiceImpl implements TransferService {
}
通过反射,判断TransferServiceImpl类上是否有注解,如果有就获取注解和注解的value
//1.获取某个类的字节码对象
Class<?> aClass = TransferServiceImpl.this.getClass();
//2.判断当前类上是否有注解
if(aClass.isAnnotationPresent(MyService.class)){
//返回true,表示TransferServiceImpl类上有指定的MyService注解
//3. 获取注解对象,进而获取注解对象的value属性 “myTransferServiceImpl”
String value = aClass.getAnnotation(MyService.class).value()
}
结论:通过反射可以操作自定义注解
1.2 自定义注解(注解本质就是一个接口)
1.2.1 语法格式:
@元注解 //用于修饰自定义注解
public @interface 注解名称 {
//自定义注解属性
}
1.2.2 元注解介绍(用于修饰自定义注解)
1. @Target元注解:表明该注解可以应用的java元素类型

2. @Retention元注解:表明该注解的生命周期

3. @Document:表明该注解标记的元素可以被Javadoc 或类似的工具文档化
4. @Inherited:表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解
1.2.3 Spring框架中定义的注解介绍
Service 注解
@Target({ElementType.TYPE}) //应用于类、接口(包括注解类型)、枚举
@Retention(RetentionPolicy.RUNTIME) //由JVM 加载,包含在类文件中,在运行时可以被获取到
@Documented //表明该注解标记的元素可以被Javadoc 或类似的工具文档化
@Component //添加了此注解 被ioc容器管理
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default ""; //默认的value值
}
Repository 注解
@Target({ElementType.TYPE}) //应用于类、接口(包括注解类型)、枚举
@Retention(RetentionPolicy.RUNTIME) //由JVM 加载,包含在类文件中,在运行时可以被获取到
@Documented //表明该注解标记的元素可以被Javadoc 或类似的工具文档化
@Component //添加了此注解 被ioc容器管理
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default ""; // 默认的value值
}
Autowired 注解
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) // 应用于构造函数、应用于方法、应用于方法的形参、应用于属性(包括枚举中的常量)、应用于注解类型
@Retention(RetentionPolicy.RUNTIME) //由JVM 加载,包含在类文件中,在运行时可以被获取到
@Documented //表明该注解标记的元素可以被Javadoc 或类似的工具文档化
public @interface Autowired {
boolean required() default true;
}
1.2.4 自定义Spring框架的注解
如果我们在自定义注解的时候可以参考spring框架中定义的注解的方式
二、自定义Spring框架 整体实现思路梳理
- 自定义工厂类BeanFactory,作为spring ioc核心类
/**
* 工厂类,生产对象(使用反射技术)
*/
public class BeanFactory {
/**
* 任务一:读取解析xml,获取包扫描路径(dao/service类所在的包)
* 任务二:通过反射技术实例化对象并且存储待用(map集合)
* 获取class/判断自定义注解@ ,决定是否实例化操作
* 任务三:自定义对象
* 任务四:判断自定义注解@MyAutoWired,决定是否进行依赖注入操作
* 任务五:判断自定义注解@MyTransactional,决定是否进行增强操作
*/
static{
//任务一:读取解析xml,获取包扫描路径
getPackage();
//任务二:通过反射技术实例化对象并且存储待用(map集合)
loadClass(packagePath);
//任务三:实例化
doInstance();
//任务四:依赖注入
doAutoWired();
//任务五:增强
doTransactional();
}
- 自定义注解@MyService
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
- 判断类上 是否有 自定义@MyService,决定是否将当前处理类交给Spring 容器管理
//1 获取aClass类字节码对象
//2 判断类上 是否有 自定义@MyService注解
if (aClass.isAnnotationPresent(MyService.class)) {
//3 获取注解的value
if (aClass.isAnnotationPresent(MyService.class)) {
annoBeanValue = aClass.getAnnotation(MyService.class).value();
}
//4 实例化bean
Object obj = aClass.newInstance();
//5 将实例化之后的对象交给容器管理,key就是注解中指定的value(如果没有指定value,可以指定类名首字母小写作为key)
ioc容器.put(annoBeanValue, obj);
}
三、具体编码介绍
public class BeanFactory {
/**
* 任务一:读取解析xml,获取包扫描路径
* 任务二:通过反射技术实例化对象并且存储待用(map集合)
* 任务三:依赖注入
* 任务四:增强
*/
private static Map<String, Object> classMap = new HashMap<>(); // 存储对象
private static Set<String> classSet = new HashSet<>(); // 存储对象的路径信息
private static String packagePath;
static {
//* 任务一:读取解析xml,获取包扫描路径
getPackage();
//* 任务二:通过反射技术实例化对象并且存储待用(map集合)
loadClass(packagePath);
// 任务三:实例化
doInstance();
//* 任务四:依赖注入
doAutoWired();
//* 任务五:增强
doTransactional();
}
}
任务一:读取解析xml,获取包扫描路径
private static void getPackage() {
// 加载xml
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 解析xml
SAXReader saxReader = new SAXReader();
Document document = null;
try {
document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> beanList = rootElement.selectNodes("//component-scan");
for (int i = 0; i < beanList.size(); i++) {
Element element = beanList.get(i);
// 处理每个base-package元素
packagePath = element.attributeValue("base-package"); // accountDao
String parentPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
System.out.println("parentPath:" + parentPath);
packagePath = parentPath + packagePath.replace(".", "/");
System.out.println("packagePath:" + packagePath);
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
任务二:通过反射技术实例化对象并且存储待用(map集合)
private static void loadClass(String packagePath) {
File scanPackage = new File(packagePath);
//递归扫描路径
File[] files = scanPackage.listFiles();
for (File file : files) {
if (file.isDirectory()) { // 子package
// 递归
loadClass(file.getPath()); // com.lagou.demo.controller
} else if (file.getName().endsWith(".class")) {
String className = scanPackage + "\\" + file.getName().replaceAll(".class", "");
className = className.split("classes")[1].replace("\\", ".").substring(1);
classSet.add(className);
}
}
System.out.println("-------------------------------------------");
classSet.forEach(path -> System.out.println("path -> " + path));
}
任务三:实例化
/**
* 通过反射实例化对象,此时暂不维护依赖注入关系
*/
private static void doInstance() {
classSet.forEach(path -> {
try {
//反射
Class<?> aClass = Class.forName(path);
//设置存储的key
String annoBeanValue = null;
//只处理有如下的注解
if (aClass.isAnnotationPresent(MyService.class) ||
aClass.isAnnotationPresent(MyComponent.class) ||
aClass.isAnnotationPresent(MyRepository.class)) {
//获取注解的value
if (aClass.isAnnotationPresent(MyService.class)) {
annoBeanValue = aClass.getAnnotation(MyService.class).value();
} else if (aClass.isAnnotationPresent(MyRepository.class)) {
annoBeanValue = aClass.getAnnotation(MyRepository.class).value();
} else if (aClass.isAnnotationPresent(MyComponent.class)) {
annoBeanValue = aClass.getAnnotation(MyComponent.class).value();
}
//如果没有指定value,则以类型首字母小写为key进行存储
if ("".equals(annoBeanValue)) {
annoBeanValue = lowerFirst(aClass.getSimpleName());
}
//实例化bean
Object obj = aClass.newInstance();
classMap.put(annoBeanValue, obj);
//service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到容器中,便于后期根据接口类型注入
Class<?>[] interfaces = aClass.getInterfaces();
if(interfaces != null && interfaces.length > 0) {
for (int j = 0; j < interfaces.length; j++) {
Class<?> anInterface = interfaces[j];
// 以接口的全限定类名作为id放入
classMap.put(
lowerFirst(anInterface.getName().substring(anInterface.getName().lastIndexOf(".")+1)),
aClass.newInstance());
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
});
}
任务四:依赖注入
/**
* 这边可以实现一个简单的循环依赖的处理
* A 可能依赖于 B ,B 可能依赖于 C ,C 可能又依赖于D,可以维护一下嵌套依赖
* static List<String> fieldsAlreayProcessed = new ArrayList<>(); // 缓存已经进行过依赖注入的信息
*
*/
private static void doAutoWired() {
classMap.forEach((keys, obj) -> {
System.out.println(keys + " / " + obj);
Field[] fields = obj.getClass().getFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (!field.isAnnotationPresent(MyAutowired.class)) {
continue;
}
//具有此注解
try {
field.setAccessible(true);
String autoWiredFieldName = lowerFirst(field.getType().getName().
substring(field.getType().getName().lastIndexOf(".")+1));
System.out.println("autoWiredFieldName:"+autoWiredFieldName);
field.set(obj,classMap.get(autoWiredFieldName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
System.out.println("doAutoWired over");
}
任务五:增强
/*
* 实现事务管理,为添加了@MyTransactional注解的对象创建代理对象,并覆盖原IOC容器中的对象
*/
private static void doTransactional() {
//目前只处理在类上添加此注解
classMap.forEach((keys, obj) -> {
Class<?> aClass = obj.getClass();
if(aClass.isAnnotationPresent(MyTransactional.class)) {
//进行增强
Class<?>[] interfaces = aClass.getInterfaces();
if(interfaces != null && interfaces.length > 0) {
// 使用jdk动态代理
classMap.put(keys,((ProxyFactory)classMap.get("proxyFactory")).getJdkProxy(obj));
}else{
// 使用cglib动态代理
classMap.put(keys,((ProxyFactory)classMap.get("proxyFactory")).getCglibProxy(obj));
}
}
});
}
本文介绍了如何通过自定义注解实现Spring框架,详细阐述了自定义注解的原理,元注解的作用,以及Spring中常用注解的定义。在实践中,创建了类似Spring的BeanFactory,通过反射读取XML配置,扫描指定包下带有自定义注解的类,并进行实例化、依赖注入和事务管理等操作。
1108

被折叠的 条评论
为什么被折叠?



