丝不润Spring IoC


Spring

丝不润是一个非常庞大的框架,真的是太蓝了。下面就很菜鸡的记录一下。
在这里插入图片描述

Spring特性之IoC组件添加

  具体啥叫Ioc控制反转,去康康其他博客。
  首先,控制反转的思想就是要将对象交给Spring容器来托管。容器托管的方式也有很多,比如用xml配置文件,注解。后面使用SpringBoot将大量的使用到注解的方式,下面就举个栗子来说说注解驱动的Spring。
  要使用Spring,首先得有Spring工程。使用Maven管理工具就很方便。
  不是很专业啊,先在Maven工程里边建一个目录结构,bean中放置对象的类。在springConfig包中放置Spring的配置类。IoCTest就是放用于测试的类。
在这里插入图片描述

  • 001.给IoC容器添加组件之-> 使用@Bean

  重点在于配置类(MainConfiguration)上,配置类类似于之前使用的xml文件。

package springConfig;
import bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration //用来说明该类是一个配置类
public class MainConfiguration {
    @Bean //说明下面的方法是会注册bean
    public Person person(){
        return new Person("zhangsan",29);
    }
}

  之后在测试类中进行测试。首先需要注册容器对象(不晓得这么说准不准确),之后通过容器对象获取bean

package IoCTest;
import bean.Person;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import springConfig.MainConfiguration;

public class Test1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext(MainConfiguration.class);
        Person person = (Person)annotationConfigApplicationContext.getBean("person");
        System.out.println(person);
    }
}

使用,就是这么使用,看起来贼简单。但是!当铺天盖地的属性配置注解袭来,还是会搞得眼花缭乱。
在这里插入图片描述

    • (xml的id怎么注解实现?)上面测试类中,通过容器getBean()中间输入的是配置类中的方法名。在配置类中的Bean注解中可以设置值。如@Bean(“hehe”),这就相当于在xml文件中的Bean标签中的id。此时在getBean的时候就应该getBean(“hehe”)。
    • (获取BeanName)annotationConfigApplicationContext 中有个功能是getBeanNamesForType,可以将某一个Bean类的所有bean的名字保存为一个String数组。如果Bean中有指定“id”那么,将优先保存Bean id,否则保存的是Bean方法名。
String[] name = annotationConfigApplicationContext.getBeanNamesForType(Person.class);
        for (String s : name) {
            System.out.println(s);
        }

所以说到getBean,肯定会发现,getBean不仅仅可以通过id来获取,还能够通过组件名来获取,也就是通过组件的类对象来获取。比如:

Student student = annotationConfigApplicationContext.getBean(Student.class);

所以 getBean(id)getBean(Class<Object>) 有啥子区别喃?
其实也很好理解,getBean(id) 就是精准打击,指哪打哪。
如果是getBean(Class<Object>) 的话就属于蒙眼抓虾,抓的是容器中Object类的Bean,那抓的是哪个Bean呢?这个问题就限制了Bean的个数,容器中这个类的Bean只能有1个或者0个(说0个不是很准确),蒙眼抓的话就只抓那一个。如果容器里存在两个Bean,那就会抛出异常,莫法接着快乐的玩耍了。
在这里插入图片描述

< Bean 的单实例多实例问题>
  Bean被加载进容器,还有一个问题就是单实例或者多实例。
默认情况下,Bean是单实例的。例如,上述两次getBean,他们拿到的是同一个实例对象。这个单实例和传统意义上的单实例还是有一定的区别的,传统意义上的单实例是指某一个类的对象只能有一个,比方说人类是单实例,那就只允许有一个人,多实例就是可以有很多人。Bean的单实例是指,Bean对象是能有一个,比方说小明这个人只有能有一个小明,多实例就可以有很多小明的克隆体。(形容不当,将就理解一下)

        Person person1 = (Person) annotationConfigApplicationContext.getBean("hehe");
        Person person2 = (Person) annotationConfigApplicationContext.getBean("hehe");
        System.out.println(person1 == person2);

上述输出结果为true。

如何设置为多实例呢?(使用@Scope)
@Scope有4种可输入属性:1.prototype 2.singleton 3.request 4.session。3,4先不说了。1就是多实例,2就是单实例(默认)。因此,在hehe方法的上面添加一个@Scope(“prototype”),上述的代码将返回false。

< Bean 的加载时机问题>
  加载时机,说白了也就是调用被@Bean修饰的方法被调用的时机。
  首先先说结论:

容器创建时getBean时
prototype不会被加载每一次get就加载一次
singleton会被加载进容器后面每一次get都不会加载

  其实也很好理解,因为多实例的模式下,每一次getBean都是要得到一个新的对象。因此每一次get都需要去调用方法来new 一个 Person的对象。所以刚刚创建容器的时候也没必要去加载,等需要了就加载去new一个。单实例的模式下,容器刚刚创建,就调用方法new一个对象放在容器当中,等需要get的时候就直接将创建好的对象拿出来。每一次getBean的时候拿到的都是同一个对象。拿同一个对象当然不需要去new,因此也就不会去加载。
  如果说在单实例的模式下,不想要再容器已创建就加载咋整?(@Lazy)
那就在方法上添加一个@lazy注解,实现懒加载模式。只有再第一次被getBean的时候才会去加载。

< Bean 的加载条件:@Conditional注解>
  如果说在有的情况下,我们需要加载Bean进容器,但有的情况下我们不想加进容器,这时候就需要用到@Conditional注解了。
  点进Conditional注解可以看到,参数位置需要输入的是Class类,类还得要实现Condition接口。

public @interface Conditional {
    Class<? extends Condition>[] value();
}

假如需要实现一个需求,写两个Bean,一个为Bill对象,一个为Linux对象。当系统为Windows系统时就只加载Bill对象,当为Linux系统时就只加载Linux对象。

	@Bean("Windows")
    @Conditional(WindowsCondition.class)
    public Person person02() { return new Person("Bill",59); }

    @Bean("Linux")
    @Conditional(LinuxCondition.class)
    public Person person03() { return new Person("Linux",39);}

  刚刚已经说过,Conditional注解中传入的为实现Condition接口的Class类,因此需要写两个类来实现Condition接口。上面的@Conditional注解已经写好了。

第一个类:WindowsCondition:

package com.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class WindowsCondition implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment(); //获取运行环境
        String osName = environment.getProperty("os.name"); //获取当前系统名称
        return osName.contains("Windows"); //如果名称中包含Windows则返回true
    }
}

第二个类:LinuxCondition:

package com.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class LinuxCondition implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();//获取运行环境
        String osName = environment.getProperty("os.name");//获取当前系统名称
        return osName.contains("Linux");//如果名称中包含Linux则返回true
    }
}

可以看到实现Condition接口的matches方法,当返回为true时,注解修饰的Bean就会被加载,反之亦然。
@Conditional注解可以作用于方法之上,也可以作用于类上。
假如吧带有LinuxCondition 的Conditional注解注释在MainConfiguration的类头上,则容器中不会加载任何组件。


  • 002.给IoC容器添加组件之-> 包扫描@ComponentScan
      包扫描注解就是将注解指定包下被@Component @Controller @Service @Repository标识的类添加到IoC容器。
    设定以下结构目录:分别给其中的类头上添加注解(图中红色@所示)
    在这里插入图片描述
    在MainConfiguration类头中添加@ComponentScan(“包名”)
    测试中还是使用容器对象的getBeanDefinitionNames(),返回一个String[] 。这个方法会将容器中的组件名返回。
    结果如下图:
    在这里插入图片描述
    如果没有添加@ComponentScan注解,则只会出现@Configuration和@Bean注释的组件。

- 003.给IoC容器添加组件之-> 导入组件@Import

  相比较包扫描@ComponentScan注解,@Import就显得更精准些。
假如在bean包中定义了其他几个类,想要在再容器创建时加载到容器当中,就得在配置类的类头上添加@Import注解。注解中传入的是需要加载进容器的类对象(Xxx.class),就是这么easy。

@Import({Student.class, Teacher.class,Worker.class})

如果一个一个的写不是很方便,可以使用ImportSelector类来进行管理。

首先,定义一个类实现ImportSelector接口,实现selectImports方法。方法的返回值为一个String[]数组,将需要加载进容器的类的全类名一一写进数组就可以了。
然后在@Import的地方只需要传入ImportSelector的实现类的类对象就ok。

ImportSelector的实现类:

package com.condition;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[] {"com.bean.Student","com.bean.Teacher","com.bean.Worker"};
    }
}

@import:

@Import({ MyImportSelector.class})

  直接使用Import进类对象和使用ImportSelector达到的效果是一样的。对了,这样被加载进容器的组件是单例的。
  现在已经说了两种import的方式,下面害有一种:ImportBeanDefinitionRegistrar接口
  ImportBeanDefinitionRegistrar接口和ImportSelector的用法是差不多的,都是需要实现这个接口,重写接口方法。然后将实现类的类对象放进Import中。
ImportBeanDefinitionRegistrar的重写方法如下:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    //AnnotationMetadata个人理解是Import注解位置的所有注解信息
    //BeanDefinitionRegistry是对容器bean注册注销等等功能的对象
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //发现下面需要一个BeanDefinition对象,就看看它的实现类,RootBeanDefinition
        //构造函数传入需要注册组件的类对象就阔以了
        RootBeanDefinition rootBeanDefinitionDoc = new RootBeanDefinition(Doctor.class);
        //通过beanDefinitionRegistry的registerBeanDefinition方法注册Bean。传入参数为Bean id和Bean的Definition对象↑
        beanDefinitionRegistry.registerBeanDefinition("doc1",rootBeanDefinitionDoc);
    }
}

就按上述代码,容器中就有了一个id为doc1的Bean。


  • 004.给IoC容器添加组件之-> 实现FactoryBean接口

通过Bean工厂来获得bean。步骤如下:

  1. 实现FactoryBean接口
  2. 重写接口方法
  3. 将实现类以@Bean的形式假如配置类
  4. 大功告成

首先实现FactoryBean接口,注意不是BeanFactory接口啊!注意不是BeanFactory接口啊!注意不是BeanFactory接口啊!
在这里插入图片描述
以建Person类的Bean为例:

package com.bean;
import org.springframework.beans.factory.FactoryBean;
public class PersonFactory implements FactoryBean<Person> {
    
    public Person getObject() throws Exception {
        return new Person(); //返回一个Person对象
    }

    public Class<?> getObjectType() {
        return Person.class; //返回Person类的类对象
    }

    public boolean isSingleton() {
        return false; //如果想要这个Bean是个单例的那就改成true
    }
}

之后在Config类中添加实现类的Bean

@Bean
public PersonFactory personFactory1(){return new PersonFactory();}

注意看这里的方法返回对象是一个PersonFactory类的对象。一会getBean有惊喜…

在这里插入图片描述
好这里来getBean,并获取Bean的类对象的名称

Object personFactory1 = annotationConfigApplicationContext.getBean("personFactory1");
System.out.println(personFactory1.getClass().getName());

结果是:com.bean.Person
在这里插入图片描述
  居然不是PersonFactory ,这个就是FactoryBean的神奇之处,它把内部方法封装的类对象放入了容器当中。那如果要想获得工厂Bean的Bean咋整?(咋那么拗口)那就在getBean的id前面加一个&
 因为,在BeanFactory接口中定义了一个前缀符,用于获取工厂对象本身。(这个地方又成了BeanFactory,具体为啥能work,我也晓不得)

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

至此,4中添加Bean的方式已经介绍完了。



Bean 的生命周期

  bean的生命周期也就是:创建->初始化->销毁
  管理其生命周期也是有4中方法,下面来说说

  • 001. @Bean
    在一个类中定义好初始化方法和销毁方法:
public class Driver extends Person {

    public Driver() { }

    public Driver(String name, int age) { super(name, age); }

    public void init(){
        System.out.println("Driver's init method is running...");
    }

    public void destory(){
        System.out.println("Driver's destory method is running...");
    }
}

在配置类中一样定义一个Bean,Bean中就可以指定初始化方法和销毁方法。

@Bean(initMethod = "init",destroyMethod = "destory")
public Driver driver(){ return new Driver("阿富",19); }

接下来创建容器,发现init方法被执行了。然后关闭容器:

annotationConfigApplicationContext.close();

destory方法也会被执行。

这是在默认单例的情况, 如果将@scope设置为prototype结果将不一样
  多例情况之前也说过,只有再getBean的时候才会加载bean。所以只创建容器的话是不会去执行init方法的,init方法只有再getBean的时候才会执行,没get一次就执行一次。
  特殊的一点是,在销毁容器的时候,destory方法是不会被执行的。

  • 002. 实现InitializingBean, DisposableBean接口

  使用这种方式,实现InitializingBean接口,重写afterPropertiesSet()方法,执行初始化工作。实现 DisposableBean接口,重写destroy()方法,执行销毁工作。这样就不需要去在@Bean中指定初始化方法和销毁方法了。下面举个栗子:

@Component
public class Client extends Person implements InitializingBean, DisposableBean {
    public Client() { System.out.println("Client's constructor..."); }

    public Client(String name, int age) {
        super(name, age);
        System.out.println("Client's constructor with parameter...");
    }

    public void destroy() throws Exception {
        System.out.println("Client's destory method...");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("Client's init method...");
    }
}
  • 003. @PostConstruct、@PreDestroy注解

  这两个注解其实光看名字就能猜到大概怎么用了,PostConstruct后于构造器,这很明显就是用来修饰初始化方法的。PreDestory预销毁,这个就是销毁方法。多的不说先,看栗子:

@Component
public class Cooker extends Person {
    public Cooker(){ System.out.println("Cooker's constructor..."); }

    @PostConstruct
    public void init(){ System.out.println("Cooker's init method..."); }

    @PreDestroy
    public void destory(){ System.out.println("Cooker's destory method..."); }
}
  • 004. 初始化的前后置通知BeanPostProcessor接口

  这个就是一个初始化方法的前后置通知,实现这个接口,重写两个方法。直接看栗子吧:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    //在Bean的初始化之前执行(前置通知)
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+"postBeforeInit...");
        return null;
    }
    //在Bean的初始化之后执行(后置通知)
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+"postAfterInit...");
        return null;
    }
}

容器中的所有bean的加载都会被初始化,因此上面的两个方法都会被执行。对于以上三中方法实现的初始化都能够被BeanPostProcessor识别,实现前后置通知。
下面给看一个结果:
在这里插入图片描述

Spring特性之IoC组件赋值

   组件加载到容器中,就涉及到一个问题就是注入问题。和xml中的properties的value赋值相类似。使用@Value注解也能够达到相同的效果。
  @value()中可以有三种方式注入:

  1. 普通类型的注入,如:@Value(“123”)、@Value(“coco”)、@Value(“false”)等
  2. spEL表达式,如@Value(#{“5-3*3”})、@Value(#{“Car.weight”})等
  3. 引入properties配置文件属性,如:@Value(${Person.nickName})

  @Value的位置是卸载组件类的属性上面的。上面所述的1,2方法直接写就好了。第三种方式需要用到properties配置文件,配置文件放于resources目录下就好。之后在配置类的类头要导入配置文件。如下图所示:
在这里插入图片描述
配置类头添加配置文件注解:

@PropertySource({"BeanProp/Person.properties"})

配置文件定义一个属性:

Person.nickName = 刘德华

之后就可以自Person类中进行属性注入了:

public class Person {

    @Value("小刘")
    private String name;
    @Value("#{3*3}")
    private int age;
    @Value("${Person.nickName}")
    private String nickName;
	...
	...
}

getBean(person),发现Bean的属性被赋了值:

在这里插入图片描述
<插一个题外话>
properties文件被加载之后,配置文件中的kv对也就被加进了环境变量。可以通过环境对象来获得属性值。

ConfigurableEnvironment environment = annotationConfigApplicationContext.getEnvironment();
String property = environment.getProperty("Person.nickName");
System.out.println(property)

显示结果:刘德华

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值