带你简化理解Spring 基于注解配置的原理
1 需求说明
- 自己写一个简单的 Spring 容器, 通过读取类的注解 (@Component @Controller @Service @Reponsitory),将对象注入到 IOC 容器
bean id= MyComponent bean 对象=
com.hj.spring.component.MyComponent@13221655bean id= UserDao bean 对象= com.hj.spring.component.UserDao@2f2c9b19
bean id= UserService bean 对象=
com.hj.spring.component.UserService@31befd9fbean id= UserController bean 对象=
com.hj.spring.component.UserController@1c20c684 i am user da
- 我们不使用 Spring 原生框架,自己手写 IO+Annotaion+反射+集合 技术实现, 打通 Spring 注解方式开发的技术难点
2 思路分析
思路分析+程序结构
我们使用注解方式完成, 不使用 xml 来配置
程序框架图
3 代码实现
● 应用实例
- 手动实现注解的方式来配置 Controller / Service / Respository / Component
- 我们使用自定义注解来完成.
● 代码实现
代码结构:
- 创建 ComponentScan.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 解读* 1. @Target(ElementType.TYPE)指定我们的ComponentScan注解可以修饰 Type程序元素* 2. @Retention(RetentionPolicy.RUNTIME) 指定ComponentScan注解 保留范围* 3. String value() default ""; 表示ComponentScan 可以传入 value*/@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {String value() default "";
}
- 创建 HJSpringConfig.java
/*** @author hj* @version 1.0* 这是一个配置类, 作用类似我们原生spring的 beans.xml 容器配置文件*/
@ComponentScan(value = "com.hj.springboot_06_ssmp.component")
public class HJSpringConfig {}
- 创建 SpringApplicationContext.java
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;/*** @author hj* SpringApplicationContext 类的作用类似Spring原生ioc容器*/
public class SpringApplicationContext {private Class configClass;//ioc我存放的就是通过反射创建的对象(基于注解方式)private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();public SpringApplicationContext(Class configClass) {this.configClass = configClass;//获取要扫描的包//1. 先得到HJSpringConfig配置的的@ComponentScan(value = "com.hj.springboot_06_ssmp.component")ComponentScan componentScan =(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);//注解System.out.println("注解内容= " + componentScan);//2. 通过componentScan的value=> 即要扫描的包String path = componentScan.value();System.out.println("要扫描的包= " + path);}public static void main(String[] args) {SpringApplicationContext springApplicationContext = new SpringApplicationContext(HJSpringConfig.class);}
}
第一步:获取要扫描的包
//1. 先得到HJSpringConfig配置的的@ComponentScan(value = "com.hj.springboot_06_ssmp.component")
ComponentScan componentScan =(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);//注解
System.out.println("注解内容= " + componentScan);//2. 通过componentScan的value=> 即要扫描的包
String path = componentScan.value();
System.out.println("要扫描的包= " + path);
运行结果:
注解内容= @com.hj.springboot_06_ssmp.annotation.ComponentScan(value=com.hj.springboot_06_ssmp.component)
要扫描的包= com.hj.springboot_06_ssmp.component
第二步:得到要扫描的包下的所有资源(类.class)
//1.得到类的加载器
ClassLoader classLoader = SpringApplicationContext.class.getClassLoader();//2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径
path = path.replace(".", "/");//一定要把. 替换成 /
URL resource = classLoader.getResource(path);
System.out.println("resource=" + resource);
运行结果:
resource=file:/F:/hspJava/SpringBoot/springboot/springboot_06_ssmp/target/classes/com/hj/springboot_06_ssmp/component
注意这里得到的资源路径是对应target文件下的:
//3. 将要加载的资源(.class) 路径下的文件进行遍历=>io
File file = new File(resource.getFile());//目录也是一个文件
System.out.println(file.isDirectory());
if(file.isDirectory()) {File[] files = file.listFiles();for (File f : files) {System.out.println("=======================");System.out.println(f.getAbsolutePath());//F:\hspJava\SpringBoot\springboot\springboot_06_ssmp\target\classes\com\hj\springboot_06_ssmp\component\UserService.class//获取到 com.hj.spring.component.UserServiceString fileAbsolutePath = f.getAbsolutePath();}
}
运行结果:
//这里我们只处理.class文件
if (fileAbsolutePath.endsWith(".class")) {//1. 获取到类名String className =fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));System.out.println("className=" + className);//2. 获取类的完整的路径(全类名)//老师解读 path.replace("/",".") => com.hspedu.spring.component.String classFullName = path.replace("/", ".") + "." + className;System.out.println("classFullName=" + classFullName);//3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..try {//这时,我们就得到该类的Class对象//Class clazz = Class.forName(classFullName)//老师说一下//1. Class clazz = Class.forName(classFullName) 可以反射加载类//2. classLoader.loadClass(classFullName); 可以反射类的Class//3. 区别是 : 上面方式后调用该类的静态方法, 下面方法不会//4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @ComponentClass<?> aClass = classLoader.loadClass(classFullName);if (aClass.isAnnotationPresent(Component.class) ||aClass.isAnnotationPresent(Controller.class) ||aClass.isAnnotationPresent(Service.class) ||aClass.isAnnotationPresent(Repository.class)) {//这里老师演示一个Component注解指定value,分配id//老师就是演示了一下机制.if(aClass.isAnnotationPresent(Component.class)) {//获取到该注解Component component = aClass.getDeclaredAnnotation(Component.class);String id = component.value();if(!"".endsWith(id)) {//不为空className = id;//替换}}//这时就可以反射对象,并放入到容器中Class<?> clazz = Class.forName(classFullName);Object instance = clazz.newInstance();//放入到容器中, 将类名的首字母小写作为id//StringUtilsioc.put(StringUtils.uncapitalize(className) , instance);}} catch (Exception e) {e.printStackTrace();}
}
4. 总代码
/*** @author hj* SpringApplicationContext 类的作用类似Spring原生ioc容器*/
public class SpringApplicationContext {private Class configClass;//ioc我存放的就是通过反射创建的对象(基于注解方式)private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();public SpringApplicationContext(Class configClass) {this.configClass = configClass;//获取要扫描的包//1. 先得到HJSpringConfig配置的的@ComponentScan(value = "com.hj.springboot_06_ssmp.component")ComponentScan componentScan =(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);//注解System.out.println("注解内容= " + componentScan);//2. 通过componentScan的value=> 即要扫描的包String path = componentScan.value();System.out.println("要扫描的包= " + path);//得到要扫描的包下的所有资源(类.class)//1.得到类的加载器ClassLoader classLoader = SpringApplicationContext.class.getClassLoader();//2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径path = path.replace(".", "/");//一定要把. 替换成 /URL resource = classLoader.getResource(path);//得到全路径System.out.println("resource=" + resource);System.out.println(resource.getFile());//3. 将要加载的资源(.class) 路径下的文件进行遍历=>ioFile file = new File(resource.getFile());//目录也是一个文件System.out.println(file.isDirectory());if(file.isDirectory()) {File[] files = file.listFiles();for (File f : files) {System.out.println("=======================");System.out.println(f.getAbsolutePath());//F:\hspJava\SpringBoot\springboot\springboot_06_ssmp\target\classes\com\hj\springboot_06_ssmp\component\UserService.class//获取到 com.hj.spring.component.UserServiceString fileAbsolutePath = f.getAbsolutePath();//这里我们只处理.class文件if (fileAbsolutePath.endsWith(".class")) {//1. 获取到类名String className =fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));System.out.println("className=" + className);//2. 获取类的完整的路径(全类名)//老师解读 path.replace("/",".") => com.hspedu.spring.component.String classFullName = path.replace("/", ".") + "." + className;System.out.println("classFullName=" + classFullName);//3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..try {//这时,我们就得到该类的Class对象//Class clazz = Class.forName(classFullName)//老师说一下//1. Class clazz = Class.forName(classFullName) 可以反射加载类//2. classLoader.loadClass(classFullName); 可以反射类的Class//3. 区别是 : 上面方式后调用该类的静态方法, 下面方法不会//4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @ComponentClass<?> aClass = classLoader.loadClass(classFullName);if (aClass.isAnnotationPresent(Component.class) ||aClass.isAnnotationPresent(Controller.class) ||aClass.isAnnotationPresent(Service.class) ||aClass.isAnnotationPresent(Repository.class)) {//这里老师演示一个Component注解指定value,分配id//老师就是演示了一下机制.if(aClass.isAnnotationPresent(Component.class)) {//获取到该注解Component component = aClass.getDeclaredAnnotation(Component.class);String id = component.value();if(!"".endsWith(id)) {//不为空className = id;//替换}}//这时就可以反射对象,并放入到容器中Class<?> clazz = Class.forName(classFullName);Object instance = clazz.newInstance();//放入到容器中, 将类名的首字母小写作为id//StringUtilsioc.put(StringUtils.uncapitalize(className) , instance);}} catch (Exception e) {e.printStackTrace();}}}}}//编写方法返回对容器中对象public Object getBean(String name) {return ioc.get(name);}public static void main(String[] args) {SpringApplicationContext springApplicationContext = new SpringApplicationContext(HJSpringConfig.class);}
}
最后如果你觉得这篇文章对你有帮助的话,请转发、收藏和点赞哦!谢谢支持!
(spring-第4回[IoC基础篇])spring基于注解的配置 基于XML的bean属性配置:bean的定义信息与bean的实现类是分离的. 基于注解的配置:bean的定义信息是通过在bean实现 ... http://ttaale.iteye.com/blog/787586 spring 基于注解的控制器配置 博客分类: spring SpringBeanServletMVCWeb 13.12. 基于 ... 转载自 Spring 基于注解的配置 基于注解的配置 从 Spring 2.5 开始就可以使用注解来配置依赖注入.而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注 ... 从源码分析 Spring 基于注解的事务 在spring引入基于注解的事务(@Transactional)之前,我们一般都是如下这样进行拦截事务的配置: <!-- 拦截器方式配置事务 --> ... 原创整理不易,转载请注明出处:Spring基于注解TestContext 测试框架使用详解 代码下载地址:http://www.zuidaima.com/share/1775574182939648. ... 点击上方 "程序员小乐"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 关注订阅号「程序员小乐」,收看更多精彩内容 每日英文 Man has to ... Spring基于注解二 上一次介绍了很多的关于spring的基本的注解,这篇文章描述一下关于Spring注解的基本的原理,从简单的例子入手 @Configuration @Import({Color. ... Spring基于注解的方式一 Spring注解简介 之前的时候我们学习的Spring都是基于Spring配置文件的形式来编写,现在很多的情况下使用SpringBoot的时候是基于注解的形式,这里我们首 ... Spring基于注解的自动装配 基于XML的自动装配是在配置文件的bean里设置autowire属性,有byType,byName的方式.而基于注解的自动装配同样是这样只不过我们直接在成员变量上直接标 ...带你简化理解Spring 基于注解配置的原理相关推荐
最新文章
热门文章