手写IOC实现过程
一.手写ioc前基础知识
1.什么是IOC(Inversion of Control 控制反转)?
IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
2.什么是DI(Dependency Injection 依赖注入)?
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
3.IOC和DI什么关系?
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
4.什么是依赖?
传统应用程序设计中所说的依赖一般指“类之间的关系”,那先让我们复习一下类之间的关系:
泛化:表示类与类之间的继承关系、接口与接口之间的继承关系;
实现:表示类对接口的实现;
依赖:当类与类之间有使用关系时就属于依赖关系,不同于关联关系,依赖不具有“拥有关系”,而是一种“相识关系”,只在某个特定地方(比如某个方法体内)才有关系。
关联:表示类与类或类与接口之间的依赖关系,表现为“拥有关系”;具体到代码可以用实例变量来表示;
聚合:属于是关联的特殊情况,体现部分-整体关系,是一种弱拥有关系;整体和部分可以有不一样的生命周期;是一种弱关联;
组合:属于是关联的特殊情况,也体现了体现部分-整体关系,是一种强“拥有关系”;整体与部分有相同的生命周期,是一种强关联;
Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源:
Bean依赖容器:也就是说Bean要依赖于容器,这里的依赖是指容器负责创建Bean并管理Bean的生命周期,正是由于由容器来控制创建Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和容器之间的依赖关系。
容器注入Bean的依赖资源:容器负责注入Bean的依赖资源,依赖资源可以是Bean、外部文件、常量数据等,在Java中都反映为对象,并且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,可以认为是传统类与类之间的“关联”、“聚合”、“组合”关系。
5.依赖注入的好处?
动态替换Bean依赖对象,程序更灵活:替换Bean依赖对象,无需修改源文件:应用依赖注入后,由于可以采用配置文件方式实现,从而能随时动态的替换Bean的依赖对象,无需修改java源文件;
更好实践面向接口编程,代码更清晰:在Bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,通过容器注入依赖实现;
更好实践优先使用对象组合,而不是类继承:因为IoC容器采用注入依赖,也就是组合对象,从而更好的实践对象组合。
采用对象组合,Bean的功能可能由几个依赖Bean的功能组合而成,其Bean本身可能只提供少许功能或根本无任何功能,全部委托给依赖Bean,对象组合具有动态性,能更方便的替换掉依赖Bean,从而改变Bean功能;
而如果采用类继承,Bean没有依赖Bean,而是采用继承方式添加新功能,,而且功能是在编译时就确定了,不具有动态性,而且采用类继承导致Bean与子Bean之间高度耦合,难以复用。
增加Bean可复用性:依赖于对象组合,Bean更可复用且复用更简单;
降低Bean之间耦合:由于我们完全采用面向接口编程,在代码中没有直接引用Bean依赖实现,全部引用接口,而且不会出现显示的创建依赖对象代码,而且这些依赖是由容器来注入,很容易替换依赖实现类,从而降低Bean与依赖之间耦合;
代码结构更清晰:要应用依赖注入,代码结构要按照规约方式进行书写,从而更好的应用一些最佳实践,因此代码结构更清晰。
从以上我们可以看出,其实依赖注入只是一种装配对象的手段,设计的类结构才是基础,如果设计的类结构不支持依赖注入,Spring IoC容器也注入不了任何东西,从而从根本上说“如何设计好类结构才是关键,依赖注入只是一种装配对象手段”。
二. 手写IOC
标记配置分为集中式管理和分散式管理,即xml文件方式和注解方式配置元数据信息,手写ioc我采用注解配置元数据方式来实现ioc的大致运行过程。
1.定义元数据配置信息
@Component/@Controller/@Repository@Service
扫描什么样的class装载到ioc容器中进行管理。
@Autowired
什么样的对象进行依赖注入。
/**
* @Classname Component
* @Description
* @Date 2020/8/6 14:54
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
/**
* @Classname Controller
* @Description TODO
* @Date 2020/8/6 14:56
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
/**
* @Classname Repository
* @Description TODO
* @Date 2020/8/6 14:58
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
/**
* @Classname Service
* @Description TODO
* @Date 2020/8/6 14:57
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
/**
* @Classname Autowired
* @Description 自动注入注解
* @Date 2020/8/6 14:28
* @Created by zhangtianci
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "";
}
2.实现ioc容器
核心功能:加载被配置标记的class文件,交给ioc容器管理。
package org.simplespring.core;
import lombok.extern.slf4j.Slf4j;
import org.simplespring.core.annotation.Component;
import org.simplespring.core.annotation.Controller;
import org.simplespring.core.annotation.Repository;
import org.simplespring.core.annotation.Service;
import org.simplespring.util.ClassUtil;
import org.simplespring.util.ValidationUtil;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Classname BeanContainer
* @Description bean容器
* <p>
* BeanContainer应该是单例的 采用内部枚举的方式实现。
*
* 应该拥有的实例方法:
* 1.boolean isLoad() 是否加载
* 2.int getSize() 获取bean个数
* 3.loadBeans(String packageName) 根据包名加载所有被配置标记的class文件/
* 4.获取/删除/增加bean及提供一些便利的方法
*
* @Date 2020/8/6 14:41
* @Created by zhangtianci
*/
@Slf4j
public class BeanContainer {
/**
* 存放所有被配置标记(xml/注解)的class,并new出一个实例对象bean
* 存放在一个map中
*/
private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();
/**
* 加载bean的注解列表
*/
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
= Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
/**
* 是否已经加载过bean
*/
private boolean loaded = false;
/**
* 获取bean容器
*
* @return
*/
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}
private enum ContainerHolder {
HOLDER;
private BeanContainer instance;
ContainerHolder() {
instance = new BeanContainer();
}
}
/**
* 是否已经加载过bean
*/
public boolean isLoad(){
return loaded;
}
/**
* 获取容器中bean的个数
*/
public int getSize(){
return beanMap.size();
}
/**
* 加载指定路径下的所有class文件
* 并将所有被配置标记(xml/注解)的class对象和new出一个实例对象放入容器
*/
public synchronized void loadBeans(String packageName){
//判断容器是否已经加载过
if (loaded){
log.warn("Container has loaded!");
return;
}
// 导出所有的class对象
Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
//为空直接return
if (ValidationUtil.isEmpty(classSet)) {
log.warn("extract nothing from packageName" + packageName);
return;
}
classSet.stream().forEach(clazz -> {
for (Class<? extends Annotation> annotationClazz : BEAN_ANNOTATION) {
if (clazz.isAnnotationPresent(annotationClazz)){
//将目标类本身作为键,目标类的实例作为值,放入到beanMap中
beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
}
}
});
loaded = true;
}
/**
* 添加一个class对象及其Bean实例
*
* @param clazz Class对象
* @param bean Bean实例
* @return 原有的Bean实例, 没有则返回null
*/
public Object addBean(Class<?> clazz, Object bean) {
return beanMap.put(clazz, bean);
}
/**
* 移除一个IOC容器管理的对象
*
* @param clazz Class对象
* @return 删除的Bean实例, 没有则返回null
*/
public Object removeBean(Class<?> clazz) {
return beanMap.remove(clazz);
}
/**
* 根据Class对象获取Bean实例
*
* @param clazz Class对象
* @return Bean实例
*/
public Object getBean(Class<?> clazz) {
return beanMap.get(clazz);
}
/**
* 获取容器管理的所有Class对象集合
*
* @return Class集合
*/
public Set<Class<?>> getClasses(){
return beanMap.keySet();
}
/**
* 获取所有Bean集合
*
* @return Bean集合
*/
public Set<Object> getBeans(){
return new HashSet<>( beanMap.values());
}
/**
* 根据注解筛选出Bean的Class集合
*
* @param annotation 注解
* @return Class集合
*/
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){
//1.获取beanMap的所有class对象
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.通过注解筛选被注解标记的class对象,并添加到classSet里
Set<Class<?>> classSet = new HashSet<>();
for(Class<?> clazz : keySet){
//类是否有相关的注解标记
if(clazz.isAnnotationPresent(annotation)){
classSet.add(clazz);
}
}
return classSet.size() > 0? classSet: null;
}
/**
* 通过接口或者父类获取实现类或者子类的Class集合,不包括其本身
*
* @param interfaceOrClass 接口Class或者父类Class
* @return Class集合
*/
public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){
//1.获取beanMap的所有class对象
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.判断keySet里的元素是否是传入的接口或者类的子类,如果是,就将其添加到classSet里
Set<Class<?>> classSet = new HashSet<>();
for(Class<?> clazz : keySet){
//判断keySet里的元素是否是传入的接口或者类的子类
if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){
classSet.add(clazz);
}
}
return classSet.size() > 0? classSet: null;
}
}
3.定义依赖注入器
核心功能:扫描被管理的bean对象,进行依赖注入。
package org.simplespring.inject;
import lombok.extern.slf4j.Slf4j;
import org.simplespring.core.BeanContainer;
import org.simplespring.inject.annotation.Autowired;
import org.simplespring.util.ClassUtil;
import org.simplespring.util.ValidationUtil;
import java.lang.reflect.Field;
import java.util.Set;
/**
* @Classname DependencyInjector
* @Description 依赖注入器
* @Date 2020/8/6 14:38
* @Created by zhangtianci
*/
@Slf4j
public class DependencyInjector {
/**
* 拥有一个Bean容器
*/
private BeanContainer beanContainer;
public DependencyInjector(){
beanContainer = BeanContainer.getInstance();
}
/**
* 执行依赖注入
*
* 1.遍历Bean容器中所有的Class对象
* 2.遍历Class对象的所有成员变量
* 3.找出被Autowired标记的成员变量
* 4.获取这些成员变量的类型
* 5.获取这些成员变量的类型在容器里对应的实例
* 6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
*/
public void doIoc(){
if(ValidationUtil.isEmpty(beanContainer.getClasses())){
log.warn("empty classset in BeanContainer");
return;
}
//1.遍历Bean容器中所有的Class对象
for(Class<?> clazz : beanContainer.getClasses()){
//2.遍历Class对象的所有成员变量
Field[] fields = clazz.getDeclaredFields();
if (ValidationUtil.isEmpty(fields)){
continue;
}
for(Field field : fields){
//3.找出被Autowired标记的成员变量
if(field.isAnnotationPresent(Autowired.class)){
Autowired autowired = field.getAnnotation(Autowired.class);
String autowiredValue = autowired.value();
//4.获取这些成员变量的类型
Class<?> fieldClass = field.getType();
//5.获取这些成员变量的类型在容器里对应的实例
Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
if(fieldValue == null){
throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClass.getName() + " autowiredValue is : " + autowiredValue);
} else {
//6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
Object targetBean = beanContainer.getBean(clazz);
ClassUtil.setField(field, targetBean, fieldValue, true);
}
}
}
}
}
/**
* 根据Class在beanContainer里获取其实例或者实现类
*/
private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
Object fieldValue = beanContainer.getBean(fieldClass);
if (fieldValue != null){
return fieldValue;
} else {
Class<?> implementedClass = getImplementedClass(fieldClass, autowiredValue);
if(implementedClass != null){
return beanContainer.getBean(implementedClass);
} else {
return null;
}
}
}
/**
* 获取接口的实现类
*/
private Class<?> getImplementedClass(Class<?> fieldClass, String autowiredValue) {
Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
if(!ValidationUtil.isEmpty(classSet)){
if(ValidationUtil.isEmpty(autowiredValue)){
if(classSet.size() == 1){
return classSet.iterator().next();
} else {
//如果多于两个实现类且用户未指定其中一个实现类,则抛出异常
throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + " please set @Autowired's value to pick one");
}
} else {
for(Class<?> clazz : classSet){//别名采用clazz.getSimpleName()简单实现
if(autowiredValue.equals(clazz.getSimpleName())){
return clazz;
}
}
}
}
return null;
}
}
4.工具包
作为框架 类加载/反射/校验等功能。
package org.simplespring.util;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @Classname ClassUtil
* @Description
* @Date 2020/8/8 15:20
* @Created by zhangtianci
*/
@Slf4j
public class ClassUtil {
public static final String FILE_PROTOCOL = "file";
/**
* 获取类加载器
* 因为项目部署在tomcat等一些web容器中
* 这些web容器加载部署在里面的apps 所以需要自定义classLoader去加载class文件
* 当我们写框架需要去通过类加载器去加载class文件时 就需要通过Thread.currentThread().getContextClassLoader()
* 拿到tomcat的自定义的classLoader去加载项目里面的class文件
* 详情 参考我写的classLoader加载相关文章
* @return
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
/**
* 通过包名加载class
*
* 点进去Class.forName()方法进去看看
*
* @CallerSensitive
* public static Class<?> forName(String className)
* throws ClassNotFoundException {
* Class<?> caller = Reflection.getCallerClass();
* return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
* }
*
* 获取到调用这个方法的class对象 然后用这个class对象的classLoader去加载这个class文件
* 所以归根结底是 拿到tomcat的自定义的classLoader去加载的class文件
* 所以没问题
* @param className class全名=package + 类名
* @return
*/
public static Class<?> loadClass(String className){
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
log.error("loadClass failed!",e);
throw new RuntimeException(e);
}
}
/**
* 通过一个class对象实例化一个实例对象(通过默认构造器)
* @param clazz
* @param accessible
* @param <T>
* @return
*/
public static <T> T newInstance(Class<?> clazz, boolean accessible){
try {
//clazz.getDeclaredConstructor() 获取指定参数类表的构造器
//这里获取默认构造器
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(accessible);
return (T)constructor.newInstance();
} catch (Exception e) {
log.error("new instance failed!");
throw new RuntimeException(e);
}
}
/**
* 设置类的属性值
*
* @param field 成员变量
* @param target 类实例
* @param value 成员变量的值
* @param accessible 是否允许设置私有属性
*/
public static void setField(Field field, Object target, Object value, boolean accessible){
field.setAccessible(accessible);
try {
field.set(target, value);
} catch (IllegalAccessException e) {
log.error("setField error", e);
throw new RuntimeException(e);
}
}
/**
* 加载包路径下的所有的class文件
* 将class对象放进set集合中
*
* 1.获取classLoader加载器
* 2.递归加载路径下的所有class文件
* @param packageName
* @return
*/
public static Set<Class<?>> extractPackageClass(String packageName){
//1.获取classLoader加载器
ClassLoader classLoader = getClassLoader();
//2.获取资源文件的的url
URL url = classLoader.getResource(packageName.replace(".","/"));
if (url == null){
log.warn("unable to retrieve anything from package: " + packageName);
return null;
}
//3.依据不同的资源类型,采用不同的方式获取资源的集合
Set<Class<?>> classSet = null;
//过滤出文件类型的资源
if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){
classSet = new HashSet<Class<?>>();
File packageDirectory = new File(url.getPath());
extractClassFile(classSet, packageDirectory, packageName);
}
//TODO 此处可以加入针对其他类型资源的处理
return null;
}
/**
* 递归加载包路径下的class文件
* @param classSet
* @param packageDirectory
* @param packageName
*/
private static void extractClassFile(Set<Class<?>> classSet, File packageDirectory, String packageName) {
if(!packageDirectory.isDirectory()){
return;
}
//如果是一个文件夹,则调用其listFiles方法获取文件夹下的文件或文件夹
File[] files = packageDirectory.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if(pathname.isDirectory()){
return true;
} else{
//获取文件的绝对值路径
String absoluteFilePath = pathname.getAbsolutePath();
if(absoluteFilePath.endsWith(".class")){
//若是class文件,则直接加载
addToClassSet(absoluteFilePath);
}
}
return false;
}
//根据class文件的绝对值路径,获取并生成class对象,并放入classSet中
private void addToClassSet(String absoluteFilePath) {
//1.从class文件的绝对值路径里提取出包含了package的类名
//如/Users/zhangtc/springframework/simple-spring/target/classes/com/zhangtianci/entity/dto/MainPageInfoDTO.class
//需要弄成com.zhangtianci.entity.dto.MainPageInfoDTO
absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
className = className.substring(0, className.lastIndexOf("."));
//2.通过反射机制获取对应的Class对象并加入到classSet里
Class targetClass = loadClass(className);
classSet.add(targetClass);
}
});
if(files == null){
return;
}
Arrays.stream(files).forEach( file -> {
extractClassFile(classSet,file,packageName);
});
}
}
package org.simplespring.util;
import java.util.Collection;
import java.util.Map;
public class ValidationUtil {
/**
* String是否为null或""
*
* @param obj String
* @return 是否为空
*/
public static boolean isEmpty(String obj) {
return (obj == null || "".equals(obj));
}
/**
* Array是否为null或者size为0
*
* @param obj Array
* @return 是否为空
*/
public static boolean isEmpty(Object[] obj) {
return obj == null || obj.length == 0;
}
/**
* Collection是否为null或size为0
*
* @param obj Collection
* @return 是否为空
*/
public static boolean isEmpty(Collection<?> obj){
return obj == null || obj.isEmpty();
}
/**
* Map是否为null或size为0
*
* @param obj Map
* @return 是否为空
*/
public static boolean isEmpty(Map<?, ?> obj) {
return obj == null || obj.isEmpty();
}
}
手写IOC实现过程的更多相关文章
- 闭关修炼180天--手写IOC和AOP(xml篇)
闭关修炼180天--手写IOC和AOP(xml篇) 帝莘 首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点. 在手写实现IOC和AOP之前(也就是打造一 ...
- 手写AOP实现过程
一.手写Aop前基础知识 1.aop是什么? 面向切面编程(AOP):是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP). 在进行OOP开发时,都是基于对组件(比如类)进行开 ...
- 初学源码之——银行案例手写IOC和AOP
手写实现lOC和AOP 上一部分我们理解了loC和AOP思想,我们先不考虑Spring是如何实现这两个思想的,此处准备了一个『银行转账」的案例,请分析该案例在代码层次有什么问题?分析之后使用我们已有知 ...
- 手写IOC实践
一.IOC 1.什么是IOC? 控制反转(英语:Inversion of Control,缩写为IoC),是[面向对象编程]中的一种设计原则,可以用来减低计算机代码之间的[耦合度]其中最常见的方式叫做 ...
- 第三节:工厂+反射+配置文件(手写IOC)对缓存进行管理。
一. 章前小节 在前面的两个章节,我们运用依赖倒置原则,分别对 System.Web.Caching.Cache和 System.Runtime.Cacheing两类缓存进行了封装,并形成了ICach ...
- 手写IOC容器
IOC(控制翻转)是程序设计的一种思想,其本质就是上端对象不能直接依赖于下端对象,要是依赖的话就要通过抽象来依赖.这是什么意思呢?意思就是上端对象如BLL层中,需要调用下端对象的DAL层时不能直接调用 ...
- 手写IOC框架
1.IOC框架的设计思路 ① 哪些类需要我们的容器进行管理 ②完成对象的别名和对应实例的映射装配 ③完成运行期对象所需要的依赖对象的依赖
- 手写SpringMVC实现过程
1. Spring Boot,Spring MVC的底层实现都是Servlet的调用. 2. Servlet的生命周期里面首先是类的初始化,然后是类的方法的调用,再次是类的销毁. 3. 创建一个spr ...
- 我自横刀向天笑,手写Spring IOC容器,快来Look Look!
目录 IOC分析 IOC是什么 IOC能够带来什么好处 IOC容器是做什么工作的 IOC容器是否是工厂模式的实例 IOC设计实现 设计IOC需要什么 定义接口 一:Bean工厂接口 二:Bean定义的 ...
随机推荐
- hihoCoder 1062 最近公共祖先·一 最详细的解题报告
题目来源:最近公共祖先·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 题目描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其 ...
- Nslookup命令的使用 - [详细]
用法一.查询IP地址 nslookup最简单的用法就是查询域名对应的IP地址,包括A记录和CNAME记录,如果查到的是CNAME记录还会返回别名记录的设置情况.其用法是: nslookup 域名 # ...
- Unity3D Demo项目开发记录
前言 经过一段时间的学习与实际开发,unity3D也勉强算是强行入门了,正所谓好记性不如烂笔头,更何况本人并非专业从事unity3D开发,会一点C#但也并不熟悉,为了避免后期遗忘,因此特意整理了一个D ...
- layui 数据表格自带的导出Excel,身份证等E+/000问题解决
layui数据表格的工具栏自带导出Excel 会将身份证等 长整数的 自动变成E+并且 后面有000.从而导致数据不能完整导出. 解决方案: 1.先下载Excel的插件包.将压缩包内的两个js放到 l ...
- 题解 洛谷 P5385 【[Cnoi2019]须臾幻境】
首先我们知道 \(n\) 个点的树有 \(n-1\) 条边,因此对于森林来说,其点数减边数即为树的个数.那么对于普通的图,求出其任意一个生成树森林,森林中树的个数即为原图中连通块的个数,也就是点数减边 ...
- JVM系列之:Contend注解和false-sharing
目录 简介 false-sharing的由来 怎么解决? 使用JOL分析 Contended在JDK9中的问题 padded和unpadded性能对比 Contended在JDK中的使用 总结 简介 ...
- 解决element上传功能清除单个文件的问题
今天,在使用 element 实现一个上传文件的功能. 接下来,要对上传的文件列表,实现删除单文件的功能. 看了 element 开发文档,发现 on-remove 没有特别的详细的说明,刚开始使用 ...
- Squeeze-and-Excitation Networks(SENet)详解
一.SENet简介 Squeeze-and-Excitation Networks(SENet)是由自动驾驶公司Momenta在2017年公布的一种全新的图像识别结构,它通过对特征通道间的相关性进行建 ...
- C#中子类对基类方法的继承、重写和隐藏
提起子类.基类和方法继承这些概念,肯定大家都非常熟悉.毕竟,作为一门支持OOP的语言,掌握子类.基类是学习C#的基础.不过,这些概念虽然简单,但是也有一些初学者可能会遇到的坑,我们一起看看吧. 子 ...
- 下载spring的路径的文章,已经试用没问题
文章:https://blog.csdn.net/ethan__xu/article/details/80273249 spring jar下载路径 http://repo.spring.io/rel ...