【Java设计模式】二十五、自定义Spring IoC

news2024/11/25 13:21:42

文章目录

  • 1、IoC类的定义
    • 1.1 定义bean相关的pojo类PropertyValue
    • 1.2 定义MutablePropertyValues类
    • 1.3 定义BeanDefinition类
  • 2、定义注册表相关类
    • 2.1 BeanDefinitionRegistry接口
    • 2.2 SimpleBeanDefinitionRegistry类
  • 3、定义解析器相关类
    • 3.1 BeanDefinitionReader接口
    • 3.2 XmlBeanDefinitionReader类
  • 4、IOC容器相关类
    • 4.1 BeanFactory接口
    • 4.2 ApplicationContext接口
    • 4.3 AbstractApplicationContext类
    • 4.4 ClassPathXmlApplicationContext类
  • 5、测试自定义的IoC
  • 6、自定义Spring IOC总结

自定义Spring框架的IOC,对下面的配置文件进行解析,并对涉及到的对象进行管理

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="userService" class="com.plat.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
    <bean id="userDao" class="com.plat.dao.impl.UserDaoImpl"></bean>
</beans>

1、IoC类的定义

1.1 定义bean相关的pojo类PropertyValue

根据上面xml的特点,定义PropertyValue类,用于封装bean标签下的property标签的各个属性。

  • property.name即对象的属性名
  • property.ref即引用参考对象
  • property.value即对象属性为基本类型或String时的赋的值
public class PropertyValue {

  private String name;  //对象的属性名
  private String ref;  //引用参考对象
  private String value;  //对象属性为基本类型或String时的赋的值


  public PropertyValue() {
  }

  public PropertyValue(String name, String ref,String value) {
    this.name = name;
    this.ref = ref;
    this.value = value;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getRef() {
    return ref;
  }

  public void setRef(String ref) {
    this.ref = ref;
  }

  public String getValue() {
    return value;
  }

  public void setValue(String value) {
    this.value = value;
  }
}

1.2 定义MutablePropertyValues类

一个bean标签可以有多个property子标签,一个property子标签对应一个PropertyValue对象,所以再定义一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象。这里实现Iterable接口,用到了迭代器模式。

public class MutablePropertyValues implements Iterable<PropertyValue> {
	
	//里面存一个个PropertyValue对象(property子标签)。复习:这里用final修饰,list对象本身只能赋值一次,但其里面的元素(对象的属性)是可变的
    private final List<PropertyValue> propertyValueList;
	
	//被final修饰了,必须在其构造方法结束之前对其赋值,因此加有参和无参构造
	
	//无参构造
    public MutablePropertyValues() {
        this.propertyValueList = new ArrayList<PropertyValue>();
    }
	
	//有参构造
    public MutablePropertyValues(List<PropertyValue> propertyValueList) {
        this.propertyValueList = (propertyValueList != null ? propertyValueList : new ArrayList<PropertyValue>());
    }

	/**
	 * 获取所有的Property对象
	*/
    public PropertyValue[] getPropertyValues() {
    	//toArray返回的是Object数组,传个new PropertyValue[0]可让其返回PropertyValue数组,当然不这么写,强转也行
        return this.propertyValueList.toArray(new PropertyValue[0]);
    }

	/**
	 * 根据PropertyValue的name属性值获取PropertyValue对象
	*/
    public PropertyValue getPropertyValue(String propertyName) {
        for (PropertyValue pv : this.propertyValueList) {
            if (pv.getName().equals(propertyName)) {
                return pv;
            }
        }
        return null;
    }
	
	/**
	 * 接口方法重写
	 * 需要返回迭代器对象
	 * 直接把List对象的迭代器扔出去
	*/
    @Override
    public Iterator<PropertyValue> iterator() {
        return propertyValueList.iterator();
    }


	/**
	 * 判断集合是否为空
	*/
    public boolean isEmpty() {
        return this.propertyValueList.isEmpty();
    }

	/**
	 * 添加一个PropertyValue对象
	 * MutablePropertyValues 的返回值类型,用于链式编程
	*/
    public MutablePropertyValues addPropertyValue(PropertyValue pv) {
        for (int i = 0; i < this.propertyValueList.size(); i++) {
            PropertyValue currentPv = this.propertyValueList.get(i);
            if (currentPv.getName().equals(pv.getName())) {
            	//判断传入的PropertyValue对象是否已在集合中,若是,则覆盖(set方法替换指定索引的对象)
                this.propertyValueList.set(i, new PropertyValue(pv.getName(),pv.getRef(), pv.getValue()));
                //也可this.propertyValueList.set(i, pv);  
                return this;
            }
        }
        //无重复
        this.propertyValueList.add(pv);
        return this;
    }

	/**
	 * 判断集合是否包含某个name属性为形参值的PropertyValue对象
	*/
    public boolean contains(String propertyName) {
        return getPropertyValue(propertyName) != null;
    }
}

【补充】:上面那个list转数组的toArray返回的是一个Object[ ],想返回一个指定类型的数组,可强转,也可传个对应类型的数组,数组长度无所谓,0也行。

return this.propertyValueList.toArray(new PropertyValue[0]);
returnPropertyValue[]) this.propertyValueList.toArray();

以上两种等价,第一种的相关源码:也是做了一个转型和数组copy

在这里插入图片描述

1.3 定义BeanDefinition类

BeanDefinition类用来封装bean信息的,主要包含id(即bean对象的名称)、class(需要交由spring管理的类的全类名)及子标签property数据。

public class BeanDefinition {

    private String id;  //bean对象的名称
    
    private String className;   //全类名
    
    private MutablePropertyValues propertyValues;  //property标签

    public BeanDefinition() {
        propertyValues = new MutablePropertyValues();
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public void setPropertyValues(MutablePropertyValues propertyValues) {
        this.propertyValues = propertyValues;
    }

    public MutablePropertyValues getPropertyValues() {
        return propertyValues;
    }
}

2、定义注册表相关类

2.1 BeanDefinitionRegistry接口

BeanDefinitionRegistry接口定义了注册表的相关操作,定义如下功能:

  • 注册BeanDefinition对象到注册表中
  • 从注册表中删除指定名称的BeanDefinition对象
  • 根据名称从注册表中获取BeanDefinition对象
  • 判断注册表中是否包含指定名称的BeanDefinition对象
  • 获取注册表中BeanDefinition对象的个数
  • 获取注册表中所有的BeanDefinition的名称
public interface BeanDefinitionRegistry {

    //注册BeanDefinition对象到注册表中
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

    //从注册表中删除指定名称的BeanDefinition对象
    void removeBeanDefinition(String beanName) throws Exception;

    //根据名称从注册表中获取BeanDefinition对象
    BeanDefinition getBeanDefinition(String beanName) throws Exception;

    boolean containsBeanDefinition(String beanName);

    int getBeanDefinitionCount();

    String[] getBeanDefinitionNames();
    
}

2.2 SimpleBeanDefinitionRegistry类

写BeanDefinitionRegistry的子实现类,该类实现了BeanDefinitionRegistry接口,定义了Map集合作为注册表容器,用来存储BeanDefinition对象 ,选择双列集合,key为BeanDefinition的名称。

public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
	
	//存BeanDefinition
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName,beanDefinition);
    }

    @Override
    public void removeBeanDefinition(String beanName) throws Exception {
        beanDefinitionMap.remove(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public String[] getBeanDefinitionNames() {
    	//toArray(new String[1])的形参是为了把toArray的Object[]返回类型转为String[],传的String[1],长度无所谓
    	//keySet拿到单列的key集合
        return beanDefinitionMap.keySet().toArray(new String[1]);
    }
}

3、定义解析器相关类

3.1 BeanDefinitionReader接口

BeanDefinitionReader是用来解析配置文件,封装成BeanDefinition对象,并在注册表BeanDefinitionRegistry中注册。接口只定义了两个规范,具体逻辑又子类去实现:

  • 获取注册表的功能,让外界可以通过该对象获取注册表对象。
  • 加载配置文件,并注册bean数据。
public interface BeanDefinitionReader {

	//获取注册表对象
    BeanDefinitionRegistry getRegistry();
	//加载配置文件并在注册表中进行注册
    void loadBeanDefinitions(String configLocation) throws Exception;
    
}

3.2 XmlBeanDefinitionReader类

根据不同的配置文件,写BeanDefinitionReader的实现类,如针对properties文件的PropertiesXmlBeanDefinitionReader类,这里写XmlBeanDefinitionReader类是专门用来解析xml配置文件的。该类实现BeanDefinitionReader接口并实现接口中的两个方法

public class XmlBeanDefinitionReader implements BeanDefinitionReader {
	
	//声明注册表对象
    private BeanDefinitionRegistry registry;

	//无参构造中对注册表对象进行赋值(赋子类)
    public XmlBeanDefinitionReader() {
        this.registry = new SimpleBeanDefinitionRegistry();
    }

    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

	/**
	 * 形参为类路径下的xml配置文件的路径
	*/
    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
		//通过当前类的类加载器获取输入流对象
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation);
        //dom4j的SAXReader对象
        SAXReader reader = new SAXReader();
        //SAXReader对象的read方法需要传入一个输入流,返回Document对象
        Document document = reader.read(is);
        //获取根标签对象,根据xml的写法,这里即beans标签
        Element rootElement = document.getRootElement();
        //解析beans标签
        parseBean(rootElement);
    }

	/**
	 * 解析beans标签
	*/
    private void parseBean(Element rootElement) {
		//获取根标签beans下的所有子标签对象(所有bean标签)
        List<Element> elements = rootElement.elements();
        //每个Element对象即一个个bean标签
        for (Element element : elements) {
            String id = element.attributeValue("id");   //获取bean标签的id属性
            String className = element.attributeValue("class"); //获取bean标签的class属性
            //将id属性和全类名属性封装到BeanDefinition对象中
            BeanDefinition beanDefinition = new BeanDefinition();  
            beanDefinition.setId(id); 
            beanDefinition.setClassName(className);
            //获取bean标签的所有property子标签
            List<Element> list = element.elements("property"); 
            //创建管理PropertyValues对象(一个property标签一个PropertyValues对象)的MutablePropertyValues对象
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
            //一个bean标签下可以有多个property标签
            for (Element element1 : list) {
            	//拿到bean标签下的一个个property标签的属性,封装到一个个PropertyValues对象中
                String name = element1.attributeValue("name");
                String ref = element1.attributeValue("ref");
                String value = element1.attributeValue("value");
                PropertyValue propertyValue = new PropertyValue(name,ref,value);
                //一个个PropertyValues对象最终交给mutablePropertyValues
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            //beanDefinition对象封装彻底完成
            beanDefinition.setPropertyValues(mutablePropertyValues);
			//将beanDefinition对象注册到注册表中存起来
            registry.registerBeanDefinition(id,beanDefinition);
        }
      
    }
}

到此,解析xml,封装成BeanDefiniton,注册到BeanDefinitonRegistry中存起来的事儿完成了。这就是BeanDefinitionReader干的事儿。注意,上面用dom4j来对xml文件进行解析,jar包坐标:

<dependency>
	<groupId>dom4j</groupId>
	<artifactId>dom4j</artifactId>
	<version>1.6.1</version>
</dependency>

以及通过当前类的类加载器获取输入流对象

InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation);

4、IOC容器相关类

4.1 BeanFactory接口

在该接口中,定义IOC容器的统一规范,即获取bean对象。

public interface BeanFactory {
	//根据bean对象的名称获取bean对象
    Object getBean(String name) throws Exception;
	//根据bean对象的名称获取bean对象,并进行类型转换
    <T> T getBean(String name, Class<? extends T> clazz) throws Exception;
}

4.2 ApplicationContext接口

BeanFactory的子接口,该接口所有的子实现类对bean对象的创建都是非延时的(立即加载),所以在该接口中定义 refresh() 方法,该方法主要完成以下两个功能:

  • 加载配置文件
  • 根据注册表中的BeanDefinition对象封装的数据进行bean对象的创建
/**
 * 非延时加载
 */
public interface ApplicationContext extends BeanFactory {
	//进行配置文件加载并进行对象创建
    void refresh() throws IllegalStateException, Exception;
}

4.3 AbstractApplicationContext类

  • 作为ApplicationContext接口的子类,所以该类也是非延时加载,所以需要在该类中定义一个Map集合,作为bean对象存储的容器。

  • 声明BeanDefinitionReader类型的变量,用来进行xml配置文件的解析,符合单一职责原则。

而BeanDefinitionReader类型属性的对象创建,则交由子类实现,因为只有子类明确到底创建BeanDefinitionReader哪儿子实现类对象。

public abstract class AbstractApplicationContext implements ApplicationContext {

	//声明解析器对象,protected修饰,更加方便子类访问
    protected BeanDefinitionReader beanDefinitionReader;
    
    //用来存储bean对象的容器(Map)   key存储的是bean的id值,value存储的是bean对象
    protected Map<String, Object> singletonObjects = new HashMap<String, Object>();

    //存储xml配置文件的路径
    protected String configLocation;
    
	/**
	 * 定义refresh方法
	 */
    public void refresh() throws IllegalStateException, Exception {

        //加载封装注册BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(configLocation);

        //初始化bean(创建Bean对象)
        finishBeanInitialization();
    }

    //bean的初始化
    private void finishBeanInitialization() throws Exception {
    	//铜鼓解析器获取注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        //获取BeanDefinition的名称
        String[] beanNames = registry.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
            //进行Bean的初始化
            getBean(beanName);
        }
    }
}

注意:该类finishBeanInitialization()方法中调用getBean()目前是BeanFactory接口的一给抽象方法,以后子类去实现,这里使用到了模板方法模式。

4.4 ClassPathXmlApplicationContext类

该类主要是加载类路径下的配置文件,并进行bean对象的创建,主要完成以下功能:

  • 在构造方法中,创建BeanDefinitionReader对象(实现类)
  • 在构造方法中,调用refresh()方法,用于进行配置文件加载、创建bean对象并存储到容器中
  • 重写父接口中的getBean()方法,并实现依赖注入操作,注入这里依赖注入的实现思路!
//Ioc容器具体的子实现类,用于加载类路径下的xml的配置文件,创建对应的Bean
public class ClassPathXmlApplicationContext extends AbstractApplicationContext{
	
	//有参构造方法,形参为xml路径
    public ClassPathXmlApplicationContext(String configLocation) {
        this.configLocation = configLocation;
        //构建XmlBeanDefinitionReader对象
        beanDefinitionReader = new XmlBeanDefinitionReader();
        try {
            this.refresh();
        } catch (Exception e) {
        }
    }

    /**
     * 实现getBean方法,根据bean的id属性值获取bean对象
     */
    @Override
    public Object getBean(String name) throws Exception {

        //父类中定义的Map集合,用来存储Bean对象
        //先看Map里有没,有则直接返回
        Object obj = singletonObjects.get(name);
        if(obj != null) {
            return obj;
        }
		//根据解析器获取注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        //根据注册表中获取到封装Bean信息的BeanDefinition对象
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);
        if(beanDefinition == null) {
            return null;
        }
        //获取全类名
        String className = beanDefinition.getClassName();
        Class<?> clazz = Class.forName(className);
        //反射创建对象
        Object beanObj = clazz.newInstance();
        //解析bean标签的property子标签,给反射得到的对象赋值(做依赖注入)
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        //遍历所有的property标签
        for (PropertyValue propertyValue : propertyValues) {
        	//获取属性的名称、值、引用参考
            String propertyName = propertyValue.getName();
            String value = propertyValue.getValue();
            String ref = propertyValue.getRef();
            //根据spring的xml语法,bean标签下的property子标签,value和ref属性肯定只有其中一个
            if(ref != null && !"".equals(ref)) {
				//获取依赖的Bean的对象,getBean方法递归
                Object bean = getBean(ref);
                //根据属性名获取set方法的名称
                String methodName = getSetterMethodNameByFieldName(propertyName);
                //反射获取Bean定义中全类名类的所有的方法对象
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if(method.getName().equals(methodName)) {
                    	//循环判断拿到set方法对象,执行这个set方法,给要返回的beanObj对象的引用属性,调用set方法赋值,赋依赖的bean对象
                    	//到此,完成了依赖注入
                        method.invoke(beanObj,bean);
                    }
                }
            }
			//如果属性是基本类型,即value有值
            if(value != null && !"".equals(value)) {
            	//拿到属性的set方法名称
                String methodName = getSetterMethodNameByFieldName(propertyName);
                //反射拿到set方法对象
                Method method = clazz.getMethod(methodName, String.class);
                //调用set方法,beanObj对象调用set方法,赋值为value
                method.invoke(beanObj,value);
            }
        }
        //将创建的对象,放到Map中,下次可直接获取,不用再反射(单例)
        singletonObjects.put(name,beanObj);
        return beanObj;
    }

    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {

        Object bean = getBean(name);
        //相比前面的getBean,多一个转型
        if(bean != null) {
            return clazz.cast(bean);
        }
        return null;
    }

	//根据属性名获取set方法的名称 name ==> setName,及set + 首字母截取并大写 + 拼装首字母以外的全部
	public static String getSetterMethodNameByFieldName(String fieldName) {
		return "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
	}
}



5、测试自定义的IoC

测试上面定义的IoC容器,能否完成xml的解析和读取,并创建出Bean。首先将上面自定义IoC的模块安装到本地仓库:

在这里插入图片描述

新建个干净的Maven工程,引入上面的自定义Ioc模块:

<dependency>
	<groupId>com.plat</groupId>
	<artifactId>myIoc_spring</artifactId>
	<version>1.0.0</version>
</dependency>

xml中定义Bean:

<bean id="userService" class="com.plat.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.plat.dao.impl.UserDaoImpl"></bean>

准备测试类:

@Setter
public class UserServiceImpl implements UserService {

	private UserDao ueserDao;   //声明个UserDao类型的对象

	public UserServiceImpl(){
		System.out.println("userService被创建了");
	}
	public void add(){
		System.out.println("userService ...");
		userDao.add();
	}
}

public class UserDaoImpl implements UserDao {
	
	public UserDaoImpl(){
		System.out.println("UserDao被创建了");
	}

	public void add(){
		System.out.println("userDao ...");
	}
}

测试:ApplicationContext和ClassPathXmlApplicationContext类均为上面自己定义的:

public class MyApplication {

	public static void main(String[] args){
		//创建Spring容器对象
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicaitonContext.xml");
		//从容器中获取Bean
		UserService bean = applicationContext.getBean("userService", UserService.class);
		bean.add();

	}
}

运行,看到Bean和依赖注入的Dao对象都成功了,且Debug可以看到依旧是非延时加载Bean。

在这里插入图片描述

6、自定义Spring IOC总结

自定义IoC使用到的设计模式:

  • 工厂模式:使用了工厂模式 + 配置文件(xml配置,
  • 单例模式:Spring IOC管理的bean对象都是单例的,此处的单例不是通过构造器进行单例的控制的,而是spring框架对每一个bean只创建了一个对象
  • 模板方法模式:AbstractApplicationContext类中的finishBeanInitialization()方法调用了子类的getBean()方法,因为getBean()的实现和环境息息相关
  • 迭代器模式:对于MutablePropertyValues类定义使用到了迭代器模式,因为此类存储并管理PropertyValue对象,也属于一个容器,所以给该容器提供一个遍历方式

spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,还有适配器模式,装饰者模式,观察者模式等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1522979.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaScript】JavaScript 运算符 ④ ( 逻辑运算符 | 逻辑与运算符 | 逻辑或运算符 || | 逻辑非运算符 ! )

文章目录 一、JavaScript 逻辑运算符1、逻辑运算符 概念2、逻辑与运算符 &&3、逻辑或运算符 ||4、逻辑非运算符 !5、完整代码示例 一、JavaScript 逻辑运算符 1、逻辑运算符 概念 JavaScript 中的 逻辑运算符 的作用是 对 布尔值 进行运算 , 运算完成 后 的 返回值 也是…

10倍提效,每天100篇,如何使用AI提取arXiv论文知识?

arXiv arXiv是国际上最有影响力的论文预发平台&#xff0c;在arXiv发表论文&#xff0c;已经成为科研圈的“潜规则”。arXiv创建于1991年&#xff0c;论文主要是理工科论文&#xff0c;包括数学、物理、计算机、统计、金融等领域。 目前收录论文数量已达200万篇。研究人员每个月…

springboot 简易文件共享工具

文章目录 一、运行界面1、登录2、展示 二、源码传送1、使用技术2、代码结构3、源码 三、运行部署1、jar方式2、docker方式3、docker-compose方式 四、优化方向 一、运行界面 1、登录 后台查看日志&#xff0c;获取token值 2、展示 批量上传文件或者点击链接下载 二、源码传…

Vulnhub - Symfonos

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Symfonos 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/symfonos-1,322/ 0x01 信息收集 …

在pzp203上运行ad9361 no-os工程

0. 环境 - pzp203 - ubuntu18 vivado2018 pzp203是一款plutosdr的国产兼容版。出厂默认是基于linux系统的&#xff0c;用libiio调用。软硬件兼容adalm-pluto。开发板提供网盘资料&#xff0c;是添加了板卡适配的。 1. hdl 1.1 准备源码 hdl https://github.com/analogdevi…

力扣162-寻找峰值, test ok

题目 代码实现 #include<iostream> #include<vector> using namespace std;class Solution { public:int findPeakElement(vector<int>& nums) {int len nums.size();int left 0, right len - 1, mid;while (left < right) {mid left (right -…

基于Java的大学计算机课程管理平台(Vue.js+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 实验课程档案模块2.2 实验资源模块2.3 学生实验模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 实验课程档案表3.2.2 实验资源表3.2.3 学生实验表 四、系统展示五、核心代码5.1 一键生成实验5.2 提交实验5.3 批阅实…

修改NLog配置文件参数的方法

目录 一、背景 二、NLog配置文件 三、C#代码 四、验证结果 ​ 五、总结 一、背景 最近项目中要用到NLog记录日志&#xff0c;有一个要求是可以灵活地修改日志文件的存放位置&#xff0c;琢磨了一小会&#xff0c;发现可以使用XML文件的形式修改文件的参数&#xff0c;现将…

实现兼容性良好的前端页面开发

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【LLM加速】注意力优化(基于位置/内容的稀疏注意力 | flashattention)

note &#xff08;1&#xff09;近似注意力&#xff1a; Routing Transformer采用K-means 聚类方法&#xff0c;针对Query和Key进行聚类&#xff0c;类中心向量集合为 { μ i } i 1 k \left\{\boldsymbol{\mu}_i\right\}_{i1}^k {μi​}i1k​ &#xff0c;其中k 是类中心的…

源于一区| 改善性能的5种高效而小众的变异策略,一键调用 (Matlab)

基于群体的优化算法在达到迭代后期时种群多样性往往会速降&#xff0c;进化将陷入停滞&#xff0c;而许多算法本身并没有突变机制&#xff0c;一旦受到局部最优值的约束&#xff0c;就很难摆脱这些约束。它还将减少种群多样性&#xff0c;减缓收敛速度。 变异策略可以增加种群…

2025武忠祥考研数学,视频百度网盘+基础全程课程PDF

“得数学者的天下”&#xff0c;25考研首先要开始的就是数学复习&#xff0c;而数学复习首先要开始的必然是高数&#xff01; 很多同学选择了跟着武忠祥老师学习高数&#xff0c;但是具体要怎么学&#xff1f;用什么书&#xff1f;怎么刷题&#xff1f;快来看看以 下的武忠祥…

GenAI开源公司汇总

主要分类如下&#xff1a; 1. 基础模型&#xff1a;这些是机器学习和AI的核心模型提供商&#xff0c;它们提供基础的算法和技术支持。 2. 模型部署与推断&#xff1a;提供云服务和计算资源&#xff0c;帮助用户部署和运行AI模型。 3. 开发者工具&#xff1a;支持AI/ML的开发…

【01】htmlcssgit

01-前端干货-html&css 防脱发神器 一图胜千言 使用border-box控制尺寸更加直观,因此,很多网站都会加入下面的代码 * {margin: 0;padding: 0;box-sizing: border-box; }颜色的 alpha 通道 颜色的 alpha 通道标识了色彩的透明度,它是一个 0~1 之间的取值,0 标识完全…

开发指南013-国际化-后台部分

平台底层做了国际化处理。开发时候根据项目性质&#xff0c;决定是否采用国际化&#xff0c;但是底层所需资源必须包含&#xff08;一些底层例如登录校验都做了对应处理&#xff09;。平台先支持中文简体、中文繁体、英文、日文&#xff0c;必要时可以随时扩展其他语言。 国际化…

单片机FLASH深度解析和编程实践(上)

本篇文章主要针对单片机FLASH编程和FLASH基本原理进行学习分享。以STM32单片机作为实例进行编程实训。 关于FLASH操作的相关寄存器及编程&#xff0c;大家可以参考下一篇文章: 单片机FLASH深度解析和编程实践&#xff08;下&#xff09;-CSDN博客 目录 一、STM32编程方式 二、…

Linux批量注释

1.注释行 1.按ctrlv进入块选择模式 &#xff0c;然后上下键选中需要注释的行 2.按shifti(也就是大写I) 然后输入// 或 # 3.按ESC键 2.取消注释行 1.按ctrlv进入块选择模式&#xff0c; 然后上下键选中需要取消注释的行 2.然后按d

QT C++ QButtonGroup应用

//QT 中&#xff0c;按钮数量比较少&#xff0c;可以分别用各按钮的信号和槽处理。 //当按钮数量较多时&#xff0c;用QButtonGroup可以实现共用一个槽函数&#xff0c;批量处理&#xff0c;减少垃圾代码&#xff0c; //减少出错。 //开发平台&#xff1a;win10QT6.2.4 MSVC…

面向控制台编程?Java的GUI开发

记得之前刚开始学习Java&#xff0c;按部就班去阅读《Java核心技术》这本书的时候&#xff0c;总是听别人提起&#xff0c;java swing那一章不用看了。然后直到对着控制台编程了半年&#xff0c;回来捡起了Swing图形界面&#xff0c;跟着网上搞了坦克大战的游戏&#xff0c;总觉…

【蓝桥杯选拔赛真题38】C++判断数字 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解析

目录 C判断数字 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、推荐资料 C判断数字 第十四届蓝桥杯青少年创意编程大赛C选拔赛真题 一、题目要求 1、编程实现 给定一个正整数N(100≤N<100000)…