spring
项目地址:点击跳转
spring配置
-
注解配置如果要生效需要加配置文件,加上componet-scan
-
自己创建的maven项目无法启动,使用骨架创建
-
踩坑:测试遇到错误Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.2:test
-
解决方案:在pom.xml中加入以下配置,在applicationContext.xml中添加头部
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> </plugins> </build>
-
添加头部
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.hodor"></context:component-scan> </beans>
-
-
使用注入容器的时候添加value指定名字
-
使用@Qualifier引入指定名字的对象
原理
程序启动的时候,将对象存入容器中
手写IOC
思路:
-
项目启动的时候,解析applicationContext.xml文件,解析XML最常见的方式是Do4j工具,解析com.hodor
-
com.hodor扫描路径下的所有类:class文件(到target目录下扫描,扫描src目录是没用的,结果如com.hodor.service.OrderService),应该是递归扫描
-
根据class的路径获取类的全路径com.hodor.service.impl.**等
-
获取所有的class的全路径,判断循环的这个类是否有@Controller或者@Service等注解,如果类上有上述的注解,这个类需要反射创建对象,然后把对象存入IOC集合中(concurrentHashMap 线程安全)
-
遍历全路径的类,反射类中的属性,使用反射检测属性上是否有@Autowired注解,如果有就需要属性的注入,匹配到零个或者多个都抛出异常
-
对外提供一个获取IOC容器的API 如context.getBean(OrderController.class),重载多种方式获取对象
实现步骤
总实现类
/**
* 2. 获取需要扫描的包的文件路径
* 3. 裁取类的全路径如 com.hodor.controller.OrderController
* 4. 反射创建类
* 5. 实现对象的依赖注入
* @param basePackage 传入值为com.hodor
*/
private void loadClasses(String basePackage) {
//2. 获取需要扫描的包的文件路径 E:\code\git\my-springIOC\target\classes\com\hodor
//扫描con.hodor下的包
//将.替换为\
basePackage = basePackage.replace(".", File.separator);
System.out.println("replace<========>" + basePackage);
URL url = Thread.currentThread().getContextClassLoader().getResource("");
System.out.println("url<=======>" + url);
String replace_url = url.toString().replace("file:/", "");
// System.out.println("replace url<=======>" + replace_url);
if(replace_url.contains("test-")) {
replace_url = replace_url.replace("test-", "");
}
File file = new File(replace_url, basePackage);
System.out.println("file<=========>" + file);
//3. 裁取类的全路径如 com.hodor.controller.OrderController
findAllClasses(file);
//4. 反射创建类
doInitInstance();
//5. 实现对象的依赖注入
doDi();
System.out.println("iocNameContainer<=====>" + iocNameContainer);
System.out.println("iocContainer<=====>" + iocContainer);
System.out.println("iocInterface<=====>" + iocInterface);
}
声明的几个类型的容器方便getBean重载获取对象
//传入配置文件名
private String springConfig;
//存放所有的全类名
private List<String> classPathes = new ArrayList<>();
//存放对象名和对应的实例,就是springIOC容器,根据名字查找
private Map<String, Object> iocNameContainer = new ConcurrentHashMap<>();
//ioc容器,以class文件作为key
private Map<Class<?>, Object> iocContainer = new ConcurrentHashMap<>();
//springIoc容器,对象实现的接口作为key,接口的是实现类作为value
private Map<Class<?>, List<Object>> iocInterface = new ConcurrentHashMap<>();
1. 解析扫描的包路径
-
自定义注解和反射
-
自定义配置文件,扫描配置文件,找到配置文件中对应的包的位置,使用dom4j进行xml解析
-
在java中解析xml的方式有4种,dom4j使用最多
-
测试代码种获取不到资源考虑是需要在test包下建立resource包
-
**踩坑:**不能使用当前线程的classloader类获取流,否则获取不到
is = Thread.class.getClassLoader().getResourceAsStream(springconfig);
-
使用以下代码更具扩展性,但是获取到的url和执行类所在的位置有关
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(springconfig);
-
解析的类和xml文件如下
-
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<component-scan base-package="com.hodor"/>
</beans>
public class SpringConfigPaser {
//获取配置文件中的扫描包属性
public static String getBasePackage(String springconfig) {
String basePackage = "";
InputStream is = null;
try {
SAXReader reader = new SAXReader();
//使用当前线程的类加载器得到得到流对象
// is = SpringConfigPaser.class.getClassLoader().getResourceAsStream(springconfig);
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(springconfig);
Document document = reader.read(is);
//得到根节点beans
Element rootElement = document.getRootElement();
Element element = rootElement.element("component-scan");
Attribute attribute = element.attribute("base-package");
basePackage = attribute.getText();
} catch (DocumentException e) {
e.printStackTrace();
} finally {
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return basePackage;
}
}
2和3. 递归扫描所有的类
- 找到component-scan对应的属性包下的所有类
- 扫描的是编译后target包下的class包中的文件
- file:/E:/code/git/my-springIOC/target/classes/org/springframework/container/
/**
* 3. 裁取类的全路径如 com.hodor.controller.OrderController
* 扫描文件路径下所有的class文件,输出文件的全路径,需要进行替换才能用于反射创建对象
* 将类的全路径存入集合中
*/
private void findAllClasses(File file) {
File[] files = file.listFiles();
for (File f : files) {
if(f.isDirectory()) {
findAllClasses(f);
} else {
String path = f.getPath();
if(path.endsWith(".class")) {
int index1 = path.lastIndexOf("\\classes");
int index2 = path.lastIndexOf(".class");
path = path.substring(index1 + 9, index2).replace("\\", ".");
classPathes.add(path);
//得到类的路径,用于反射创建对象
// System.out.println(path);
}
}
}
//拿到文件的路径 E:\code\git\my-springIOC\target\classes\com\hodor\service\impl\OrderServiceImpl.class
//通过反射创建类 只需要com后面的路径,类似于com.hodor.service.impl.OrderServiceImpl
}
4. 反射创建类
- 框架底层调用的是无参构造
- 判断是否带注解,有注解的放入ConcurrentHashMap中
- 判断注解是否带value,带value的注解创建对象使用value作为对象名,不能有同name的对象
- 使用多个ConcurrentHashMap实现不同的方式从容器中获取对象(不同的getBean方式)
/**
* 4. 反射创建类
* 判断有注解的才需要实例化
* 需要考虑注解中有value的情况,就不能使用默认的类名作为对象名
*/
private void doInitInstance() {
try {
for (String classPath : classPathes) {
Class<?> c = Class.forName(classPath);
//判断实例化的这个类是否带了注解
if(c.isAnnotationPresent(Controller.class) || c.isAnnotationPresent(Service.class)) {
//实例化
Object instance = c.newInstance();
Controller controllerAnno = c.getAnnotation(Controller.class);
if(controllerAnno != null) {
String value = controllerAnno.value();
String simpleName = "";
//如果注解有值就使用指定的值作为名字创建对象
if(value == null || "".equals(value)) {
simpleName = c.getSimpleName();
simpleName = simpleName.toLowerCase().charAt(0) + simpleName.substring(1);
} else {
simpleName = value;
}
// System.out.println("simpleName<=======>" + simpleName);
//扩展可以使用三种不同的方式获取对象
//通过对象名字获取对象,重复的对象名就报错
if(iocNameContainer.containsKey(simpleName)) {
throw new Exception("The bean name had already existed in the container");
}
iocNameContainer.put(simpleName, instance);
//通过class文件过去对象
iocContainer.put(c, instance);
//通过接口获取对象
Class<?>[] interfaces = c.getInterfaces();
for (Class<?> anInterface : interfaces) {
List<Object> byInterface = iocInterface.get(anInterface);
if(byInterface != null) {
byInterface.add(instance);
iocInterface.put(anInterface, byInterface);
} else {
List<Object> byInstance = new ArrayList<>();
byInstance.add(instance);
iocInterface.put(anInterface, byInstance);
}
}
}
Service serviceAnnotation = c.getAnnotation(Service.class);
if(serviceAnnotation != null) {
String simpleName = "";
String value = serviceAnnotation.value();
if(value == null || "".equals(value)) {
simpleName = c.getSimpleName();
simpleName = simpleName.toLowerCase().charAt(0) + simpleName.substring(1);
} else {
simpleName = value;
}
// System.out.println("simpleName<=======>" + simpleName);
if(iocNameContainer.containsKey(simpleName)) {
throw new Exception("The bean name had already existed in the container");
}
iocNameContainer.put(simpleName, instance);
iocContainer.put(c, instance);
Class<?>[] interfaces = c.getInterfaces();
for (Class<?> anInterface : interfaces) {
List<Object> byInterface = iocInterface.get(anInterface);
if(byInterface != null) {
byInterface.add(instance);
iocInterface.put(anInterface, byInterface);
} else {
List<Object> byInstance = new ArrayList<>();
byInstance.add(instance);
iocInterface.put(anInterface, byInstance);
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
5. @Autowired注入
-
官方实现的效果:可以指定name也可以不指定,但是不指定的时候如果有多个实现类(类属性为接口)会报错
-
注入容器中的对象需要处理Autowired注解,否者类中用到该注解的属性默认为空,如OrderController中注入的OrderService
-
getBean用不同的方式获取对象,遍历iocContainer中的对象,遍历每个对象中的属性,如果带有@Auto注解属性就需要从容器中找到一个赋值,找不到就报错
-
注入的时查找的顺序
- 通过value的值从容器中获取对象
- 通过class从容器中获取对象
- 通过interface从容器中获取对象(如果注入的属性是接口且有多个实现类且未命名value会报错)
-
可以扩展dao层,使用假数据测试,不声明新注解使用自定义的@Service或者@Controller能实现
/**
* 5. 实现依赖注入
*/
private void doDi() {
Set<Class<?>> classes = iocContainer.keySet();
if(classes != null) {
//通过class遍历所有的对象
for (Class<?> aClass : classes) {
//获取声明的属性的集合
Field[] declaredFields = aClass.getDeclaredFields();
if(declaredFields != null) {
for (Field declaredField : declaredFields) {
if(declaredField.isAnnotationPresent(Autowired.class)) {
//类中的属性需要依赖注入,给属性赋值,三种情况都要考虑(根据名字、class、接口从容器中找对象)
Autowired autowired = declaredField.getAnnotation(Autowired.class);
String value = autowired.value();
Object bean = null;
//如果@Autowired的属性不为空
if(!"".equals(value)) {
bean = getBean(value);
if(bean == null) {
throw new RuntimeException("No bean of this type name '" + value + "' available;expected at least 1 bean in the container");
}
} else {
//获取字段的类型
Class<?> keyClass = declaredField.getType();
//根据class文件获取,比如直接声明属性是接口实现类的情况,或者直接声明属性不带接口(如本例的controller)
bean = getBean(keyClass);
if(bean == null) {
//如果根据class找不到,就根据接口类匹
Object beanByInterface = getBeanByInterface(keyClass);
if(beanByInterface == null) {
throw new RuntimeException("No qualifying bean of type '" + aClass + "' available");
}
}
}
try {
//注入的属性在容器中匹配到了,通过反射注入属性,设置权限为可以设置
declaredField.setAccessible(true);
//通过class方式获取到对象比较合适,三种方式获取到的对象是相同的(如果能获取到)
declaredField.set(iocContainer.get(aClass), bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
}