JAVA模拟Spring实现IoC过程(附源码)
前言:本人大四学生,第一次写博客,如果有写得不好的地方,请大家多多指正
一、IoC(Inversion of Control)反转控制
传统开发都是需要对象就new,但这样做有几个问题:
- 效率低下,创建对象时比较耗时,我立马要用对象,可系统说让你等一下,创建和初始化对象需要一定的时间。
- 对象关联关系,例如:用户有所属部门,那创建用户对象时,如果部门对象不存在,还得创建部门对象。
- 代码耦合度较高
于是有人就提出了IoC控制反转概念,干嘛我不先创建好呢?如果用户要使用时,我都已经创建好了,用户不就能立马使用了?这就有点像好莱坞法则:“你别找我,我去找你”。
IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活;
我个人对IoC是这样简单理解的:
1)对象不是new出来的,而是通过反射事先创建出来的
2)既然事先创建出来,在用之前就得找个地方放起来,放哪里呢?就放在一个Map<String, Object>集合中
二、实现IoC过程中需要考虑的一些问题
• 怎么获取要创建的对象?
• 怎么把目录名转换为类名?
• 怎么把类名转换为beanName?
• 哪些类需要创建对象?怎么过滤掉不需要创建对象的类?
• 容器中该怎么存放对象?
• 怎么调用容器中存放的对象?
• 设计分层结构:高内聚,低耦合(相当于专做一件事,不要把使用功能都写在一起)
三、模拟Spring实现IoC
在这里简单模拟查询用户信息,实际应用中更复杂,但是万变不离其宗,弄懂原理才是重要的
1)架构图
2)代码结构图
在IDEA中的结构示意图(eclipse也差不多,我用的是IDEA):
各包名以及文件名含义(文件名可能会和架构图中的一些文件名不一致,因为架构图是之前画的,但不影响阅读,大同小异):
annotation包:用来存放注解,在本案例中需要创建对象的类为控制层类、业务层类、持久层类;所有需要3个注解,通过注解的方式来过滤掉不需要创建对象的类,Controller注解是加在控制层,Service注解加在业务层,Respository注解加在持久层;3个注解都是位于类上,保留到运行时期;
pojo包:设置一个pojo类,里面含有一些简单属性;
dao包:模拟持久层,由于本案例是一个简单例子,主要用于初学者查看,所有没有安装数据库,正常情况下持久层应该与数据库进行对接,没有安装数据库,所有就由持久层模拟数据库;
service包:模拟业务层,业务层调用持久层;
controller包:模拟控制层,控制层调用业务层;
utils包:工具类包;
parse包:因为我们采用了分层结构,所以在使用之前,应当把这些组件扫描加载到一起;现在都是面向接口进行开发,这样可以提高程序的灵活性,面向接口开发,同时也体现了JAVA的多态性;
Run文件就相当于整个程序的入口。
四、案例源码
1)pojo类(User.java)
package spring.pojo; /**
* @Auther: 林安杰
* @Date: 2019/9/28 19:28
* @Description:创建pojo对象,简单设置属性以及get,set,toString方法
*/
public class User {
private Integer id;
private String name; public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
2)注解
①Respository.java
package spring.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @Auther: 林安杰
* @Date: 2019/9/28 19:31
* @Description:dao层类注解
*/ @Target(ElementType.TYPE) // 注解加在类上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时
public @interface Respository {
}
②Service.java
package spring.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @Auther: 林安杰
* @Date: 2019/9/28 19:33
* @Description:业务层注解
*/ @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
③Controller.java
package spring.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @Auther: 林安杰
* @Date: 2019/9/28 19:34
* @Description:控制层注解
*/ @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
3)持久层(UserDao.java)
package spring.dao; import spring.annotation.Respository;
import spring.pojo.User; import java.util.ArrayList;
import java.util.List; /**
* @Auther: 林安杰
* @Date: 2019/9/28 19:36
* @Description:dao层,模拟数据库数据
*/ @Respository
public class UserDao {
// 模拟查询一条记录,根据进行id查询
public User selectOne(Integer id) {
User user = new User();
user.setId(id);
user.setName("林安杰");
return user;
} // 模拟查询所有记录
public List<User> get() {
// 用一个集合来存放所有对象
List<User> userList = new ArrayList<>(); User u1 = new User();
u1.setId(6);
u1.setName("linanjie");
userList.add(u1); User u2 = new User();
u2.setId(7);
u2.setName("linlin");
userList.add(u2);
return userList;
} }
4)业务层(UserService.java)
package spring.service; import spring.annotation.Service;
import spring.dao.UserDao;
import spring.pojo.User; import java.util.List; /**
* @Auther: 林安杰
* @Date: 2019/9/28 19:48
* @Description:业务层,从dao层中获取数据
*/ @Service
public class UserService {
private UserDao userDao; // 把dao层的对象设置为私有成员变量,才能访问dao层中的方法(组合) // 关系绑定(set注入)
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
} // 查询一条记录
public User getOne(Integer id) {
return userDao.selectOne(id);
} // 查询所有记录
public List<User> queryAll() {
return userDao.get();
}
}
5)控制层(UserController.java)
package spring.controller; import spring.annotation.Controller;
import spring.pojo.User;
import spring.service.UserService; import java.util.List; /**
* @Auther: 林安杰
* @Date: 2019/9/28 19:53
* @Description:控制层,调用业务层
*/ @Controller
public class UserController {
private UserService userService; // 把业务层对象设置为私有成员变量 // set注入,实现关系绑定
public void setUserService(UserService userService) {
this.userService = userService;
} // 查询一条记录
public User getOne(Integer id) {
return userService.getOne(id);
} // 查询多条记录
public List<User> queryAll() {
return userService.queryAll();
}
}
6)工具类
①FileUtil.java
package spring.utils; import java.io.File;
import java.util.List; /**
* @Auther: 林安杰
* @Date: 2019/9/28 19:58
* @Description:文件扫描工具类
*/ public class FileUtil {
// 扫描所有.class文件,并存入数组中
public static List<String> getFileNameList(String dir, List<String> fileNameList) {
File file = new File(dir); // 把string类型转换为file类型
File[] files = file.listFiles(); // 遍历当前路径下的所有文件(遍历出来的是:所有文件名+所有目录名)
for (File f :
files) {
if (f.isDirectory()) { // 判断当前文件是不是目录,是的话就递归遍历目录中的内容
getFileNameList(f.getAbsolutePath(), fileNameList);
} else {
fileNameList.add(f.getAbsolutePath()); // 不是目录就是文件,将文件添加到数组当中
}
}
return fileNameList;
}
}
②CoverUtil.java
package spring.utils; import java.util.List; /**
* @Auther: 林安杰
* @Date: 2019/9/28 21:30
* @Description:统一把路径符替换为“/”,路径符有“//”、“\”,统一后就没有差异性了
*/
public class CoverUtil {
// 从存放所有class文件的fileNameList集合中取出每一个文件名(全局限定名),替换为类名形式(包名.类名) /**
* @param baseDir 基准路径
* @param fileNameList 存放所有文件全集限定名的数组
* @return
*/
public static List<String> getClassNameList(String baseDir, List<String> fileNameList) {
// 把基准路径中的路径符统一
baseDir = baseDir.replace("\\", "/");
for (int i = 0; i < fileNameList.size(); i++) {
// 获取数组中的元素
String fileName = fileNameList.get(i); // 把元素(全集限定名)中的路径统一
fileName = fileName.replace("\\", "/"); // 把全集限定名中前面一样的基准路径替换为空串
fileName = fileName.replace(baseDir, ""); // 把替换掉的字符串去掉.class后缀
int pos = fileName.lastIndexOf(".");
fileName = fileName.substring(0, pos); // 把字符串的路径符替换为“.”
fileName = fileName.replace("/", "."); // 把最终的类名继续放回数组中
fileNameList.set(i, fileName);
}
return fileNameList;
}
}
③BeanNameUtil.java
package spring.utils; /**
* @Auther: 林安杰
* @Date: 2019/9/28 23:06
* @Description:把className转换为beanName
*/
public class BeanNameUtil {
public static String getbeanName(String className) {
// 截取后面的类名
int pos = className.lastIndexOf(".") + 1;
className = className.substring(pos);
// 类名的第一个字母小写,后面的不变
String beanName = className.toLowerCase().charAt(0) + className.substring(1);
return beanName;
}
}
7)组件扫描
①BeanFactory.java
package spring.parse; import java.util.List; /**
* @Auther: 林安杰
* @Date: 2019/9/28 23:26
* @Description:面对接口开发;需要设计一个接口
*/
public interface BeanFactory {
public List<String> scan(String dir); public void creat(String baseDir, List<String> fileNameList) throws Exception; public <T> T getObject(String beanName);
}
②ComponentScan.java
package spring.parse; import spring.annotation.Controller;
import spring.annotation.Respository;
import spring.annotation.Service;
import spring.utils.BeanNameUtil;
import spring.utils.CoverUtil;
import spring.utils.FileUtil; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; /**
* @Auther: 林安杰
* @Date: 2019/9/28 22:30
* @Description:扫描所有需要的组件,并利用反射创建对象,把对象放在容器中
*/
public class ComponentScan implements BeanFactory{ private static final Map<String, Object> beans = new HashMap<>(); // 扫描所有class文件
public List<String> scan(String dir) {
List<String> fileNameList = new ArrayList<>();
FileUtil.getFileNameList(dir, fileNameList);
// 遍历查看数组的内容
for (String fileName :
fileNameList) {
System.out.println(fileName);
}
return fileNameList;
} // 通过反射根据注解创建对象,并把对象放在容器中
public void creat(String baseDir, List<String> fileNameList) throws Exception {
List<String> classNameList = CoverUtil.getClassNameList(baseDir, fileNameList); // 遍历类名数组查看内容,并根据是否有注解创建对象
for (String className : classNameList) {
String beanName = "";
Class<?> clazz = Class.forName(className);
// 获取类上的注解
Controller ca = clazz.getAnnotation(Controller.class);
Service sa = clazz.getAnnotation(Service.class);
Respository ra = clazz.getAnnotation(Respository.class); // 若注解不为空,就创建对象
if (ca != null || sa != null || ra != null) {
Object obj = clazz.newInstance(); /*把对象存放在容器中,容器为Map键值对,值就是对象,key用className表示太复杂,把className转换成beanName
className的形式:spring.pojo.User;beanName的形式:user;所以需要写一个转换的方法
*/
beanName = BeanNameUtil.getbeanName(className); // 把对象存放到容器当中
beans.put(beanName, obj);
}
System.out.println(className + " | " + beanName);
} // 查看容器中存放的对象
System.out.println("\n容器中存放的对象为:");
for (String key : beans.keySet()) {
System.out.println(beans.get(key));
}
} // 从容器中获取对象
public <T> T getObject(String beanName){
return (T) beans.get(beanName);
}
}
8)程序入口(Run.java)
package spring; import spring.controller.UserController;
import spring.dao.UserDao;
import spring.parse.BeanFactory;
import spring.parse.ComponentScan;
import spring.pojo.User;
import spring.service.UserService; import java.util.List; /**
* @Auther: 林安杰
* @Date: 2019/9/29 12:53
* @Description:程序入口
*/
public class Run {
public static void main(String[] args) throws Exception {
// 面对接口开发,体现了多态性(向上造型)
BeanFactory context = new ComponentScan(); // 先扫描所有class文件
System.out.println("第一步,扫描当前主目录下的所有class文件:");
// 不能把路径写死,可以根据API获取路径
String baseDir = Run.class.getResource("/").getPath().substring(1);
String packageDir = Run.class.getPackage().getName();
String dir = baseDir + packageDir.replace(".", "/");
List<String> fileNameList = context.scan(dir); System.out.println("\n第二步,获得className,并且用beanName把对象存入容器中:");
context.creat(baseDir, fileNameList); System.out.println("\n第三步,业务处理:");
// 不能用new创建对象,需要从容器中获取对象
UserController userController = context.getObject("userController");
UserService userService = context.getObject("userService");
UserDao userDao = context.getObject("userDao"); // 关系注入,调用set方法
userController.setUserService(userService);
userService.setUserDao(userDao); System.out.println("查询一条记录:");
User user = userController.getOne(1);
System.out.println(user); System.out.println("查询所有记录:");
List<User> users = userController.queryAll();
for (User u : users) {
System.out.println(u);
}
}
}
五、运行结果
这就是本期分享的内容,大家可以相互学习
JAVA模拟Spring实现IoC过程(附源码)的更多相关文章
- 干货:Java多线程详解(内附源码)
线程是程序执行的最小单元,多线程是指程序同一时间可以有多个执行单元运行(这个与你的CPU核心有关). 在java中开启一个新线程非常简单,创建一个Thread对象,然后调用它的start方法,一个 ...
- spring mvc 启动过程及源码分析
由于公司开源框架选用的spring+spring mvc + mybatis.使用这些框架,网上都有现成的案例:需要那些配置文件.每种类型的配置文件的节点该如何书写等等.如果只是需要项目能够跑起来,只 ...
- 用 Java 实现人脸识别功能(附源码)
整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 更多优选 一口气说出 9种 分布式ID生成方式,面试官有点懵了 ...
- 【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)
我们前面的三篇博文,简单易懂的介绍了为什么要使用IOC[实例讲解](二).和Spring的IOC原理[通俗解释](三)以及依赖注入的两种常用实现类型(四),这些都是刚开始学习Spring IoC容器时 ...
- spring加载过程,源码带你理解从初始化到bean注入
spring在容器启动时,容器正式初始化入口refresh()如下图 ①包括初始化FactoryBean.解析XML注册所有BeanDefinition信息 ②包括注册scope管理类 ③初始化单 ...
- 使用Java生成word文档(附源码)
当我们使用Java生成word文档时,通常首先会想到iText和POI,这是因为我们习惯了使用这两种方法操作Excel,自然而然的也想使用这种生成word文档.但是当我们需要动态生成word时,通常不 ...
- 十大经典排序算法(java实现、配图解,附源码)
前言: 本文章主要是讲解我个人在学习Java开发环境的排序算法时做的一些准备,以及个人的心得体会,汇集成本篇文章,作为自己对排序算法理解的总结与笔记. 内容主要是关于十大经典排序算法的简介.原理.动静 ...
- arcgis api 4.x for js 集成 Echarts4 实现模拟迁徙图效果(附源码下载)
前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 4.x for js:esri 官网 api,里面详细的介绍 arcgis api 4.x 各个类 ...
- Java Web开发框架Spring+Hibernate整合效果介绍(附源码)
最近花了一些时间整合了一个SpringMVC+springAOP+spring security+Hibernate的一套框架,之前只专注于.NET的软件架构设计,并没有接触过Java EE,好在有经 ...
随机推荐
- 调用SM30数据表维护的函数
相关文章:http://www.cnblogs.com/caizjian/p/3248499.html 1.se11进去新建一个数据表 2.se55进去生产表维护 3.sm30进去维护数据表 4.se ...
- Linq 高级应用实例
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Tex ...
- fiddler 手机抓包,CS端抓包 使用记录
1.允许远程连接 2.忽略https证书错误 3.设置代理 4.重启fiddle 5.PC客户端抓包分工具FIddler+Proxifer https://blog.csdn.net/sunbo_cs ...
- Wireshark中的结果分析
Header checksum: 0x9899 [validation disabled] 因为,wireshark不自动做tcp校验和的检验.原因是因为:有时tcp校验和会由网卡计算,因此wires ...
- Kubernetes部署DNS
前言 阅读地址 http://thoreauz.com/2017/04/16/docker/Kubernetes%E9%83%A8%E7%BD%B2DNS%E5%92%8CDashboard/ Kub ...
- Swagger详解(SpringBoot+Swagger集成)(转)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/ai_miracle/article/de ...
- Centos7.5中的SElinux操作命令说明
设置Selinux模式 setenforce 0 0表示警告模式 1表示强制模式 关闭要设置/etc/sysconfig/selinux下将"SELINUX=enforcing"改 ...
- Julia 语言
同时安装多个库 Pkg.add.(["IJulia", "Combinatorics", "Plots", "TaylorSeri ...
- APP稳定性测试-monkey执行
Monkey命令行可用的全部选项 *示例 : adb shell monkey -p cn.lejiayuan.alpha --pct-touch 30 --pct-motion 15 --pct-t ...
- Linux C编程
Linux C网络编程 1.Linux套接字 1.1 套接字介绍 套接字(Sockets),即为网络进程ID,是由运行这个进程的计算机的IP地址和这个进程使用的端口(Port)组成. 可以只用'net ...