Spring注解开发不完全の指南V1.0

Spring注解开发不完全の指南V1.0

一、组件注册

@Configuration+@Bean给容器中注册组件

​ @Bean:给容器中注册一个Bean,类型为返回值的类型,id默认是用方法名。@Bean注解可以设置别名,当@Bean中设置得别名和方法名不同时,注解中的别名优先作为id。

@Configurable
public class MainConfig {

	@Bean("person-sp")
	public Person person(){
		return new Person("隔壁老王",5);
	}
}

测试:

@Test
public void test01(){
    AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig.class);
    Person bean=context.getBean(Person.class);
    System.out.println(bean);

    String[] names=context.getBeanNamesForType(Person.class);
    for(String name:names)
        System.out.println(name);
}

输出:

Person{name='隔壁老王', age=5}
person-sp
@ComponentScan自动扫描+指定扫描规则

测试:

	@Test	
	public void test02(){
		AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig.class);
		//获取容器中的组件名称
		String[] names=context.getBeanDefinitionNames();
		for(String name:names)
			System.out.println(name);
	}

输出:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookController
bookDao
bookService
person-sp

ComponentScan接口中有这样两个方法,在包扫描的时候只扫描想要的几个类或者排除掉不想扫描的类:

//扫描时包括哪些
Filter[] includeFilters() default {};
//扫描时排除哪些
Filter[] excludeFilters() default {};

​ 可以看到返回值都是Filter类型的数组,根据FilterType的不同,我们可以选择类型也可以选择正则表达式或者注解等作为过滤的条件。

@ComponentScan(value="com.demo",excludeFilters={
		@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class, Service.class})
})

输出:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookDao
person-sp

​ 如果将excludeFilters改成includeFilters,再次运行会发现并不起作用,回想通过xml配置时,如果想只扫描某几个包,那么需要将useDefault设置为false,因为默认是排除哪些包,同样的在注解中,我们也需要设置useDefaultFilters为false。

@ComponentScan(value="com.demo",includeFilters={
		@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class, Service.class})
},useDefaultFilters = false)
自定义TypeFilter指定过滤规则

FilterType.ANNOTATION:按照注解

FilterType.ASSIGNABLE_TYPE:按照指定的类型

@ComponentScan(value="com.demo",includeFilters={
		@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes = BookService.class),
		@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters = false)

FilterType.CUSTOM:自定义规则

@ComponentScan(value="com.demo",includeFilters={
		@ComponentScan.Filter(type =FilterType.CUSTOM,classes = MyTypeFilter.class),
		@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes = BookService.class),
		@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters = false)

由于自定义的这个过滤规则返回的都是false,也就是说没有一个是匹配的,所以并没有打印bookDao等信息。如果我们把下面代码中的注解去掉,那么打印的结果又会不同了!

package com.demo.config;

import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.core.io.Resource;

import java.io.IOException;

public class MyTypeFilter implements TypeFilter {
	/**
	  * @Date: 2020/4/26 17:47
	  * @Param metadataReader:读取到的当前正在扫描的类的信息
	  * @Param metadataReaderFactory:可以获取到其它任何的类信息
	  * @return: boolean
	  **/

	@Override
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
		//获取当前类注解的信息
		AnnotationMetadata data=metadataReader.getAnnotationMetadata();
		//获取当前正在扫描的类的类信息
		ClassMetadata classMetadata=metadataReader.getClassMetadata();
		//获取当前类资源(类的路径)
		Resource resource=metadataReader.getResource();

		String className = classMetadata.getClassName();
		System.out.println("======"+className+"======");
        //如果包含er那么可以被扫描进去
		//if(className.contains("er"))
			//return true;
		return false;
	}
}

输出:

======com.demo.MainTest======
======com.demo.bean.Person======
======com.demo.config.MyTypeFilter======
======com.demo.controller.BookController======
======com.demo.dao.BookDao======
======com.demo.service.BookService======
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
person-sp

FilterType.REGEX:使用正则表达式

FilterType.ASPECTJ:使用ASPECTJ表达式

@Scope:设置组件作用域

​ 获取到的Bean默认是单实例,也就是无论获取多少次,获取到的都是同一个。我们可以通过@Scope注解进行作用域的修改。

	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE:prototype
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON:singleton
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST:request
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION:session

​ prototype是多实例的,ioc创建完成之后获取这个bean的时候才会进行创建该bean。默认是singleton单实例的,ioc容器启动就会调用方法创建对象然后放到ioc容器中。后两个都是在web环境中的,暂不介绍。

测试:

public void test03(){
		AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig2.class);
		Person bean=context.getBean(Person.class);
		Person bean2=context.getBean(Person.class);
		//单实例打印true,多实例为false
		System.out.println(bean==bean2);
	}

MainConfig2代码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig2 {

	@Scope("prototype")
	@Bean("person")
	public Person person(){
		return new Person("刘某人",18);
	}
}

@Lazy:bean懒加载

​ 这是针对于单实例bean来说的,容器启动时先不创建对象,当第一次使用Bean的时候再创建对象然后初始化。

@Conditional:按照条件注册bean

​ 接下来我们在添加两个人Tom和Mike,假设Tom喜欢用Windows,Mike喜欢Linux系统,如果是Windows系统,容器中注册Tom,如果是Linux系统则注册Mike。

测试:

public void test04(){
		AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig2.class);
		String[] beanNamesForType = context.getBeanNamesForType(Person.class);
		for(String name:beanNamesForType)
			System.out.println(name);
	}

相关代码:

public class LinuxCondition implements Condition {
		@Override
		public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
			ConfigurableListableBeanFactory beanFactory=context.getBeanFactory();
			//获取当前环境信息
			Environment environment = context.getEnvironment();

			String property = environment.getProperty("os.name");
			if(property.contains("linux"))
				return true;
			return false;
		}
}
public class WindowsCondition implements Condition {
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		//获取到ioc使用的beanfactory
		ConfigurableListableBeanFactory beanFactory=context.getBeanFactory();
		//获取类加载器
		ClassLoader classLoader = context.getClassLoader();
		//获取当前环境信息
		Environment environment = context.getEnvironment();
		//获取到当前bean定义的注册类
		BeanDefinitionRegistry registry = context.getRegistry();

		String property = environment.getProperty("os.name");
		if(property.contains("Windows"))
			return true;
		return false;
	}
}

在MainConfig2.java中添加:

@Conditional(WindowsCondition.class)
@Bean("Tom")
public Person person01(){
    return new Person("Tom",34);
}

@Conditional(LinuxCondition.class)
@Bean("Mike")
public Person person02(){
    return new Person("Mike",29);
}

输出:

person
Tom

​ @Conditional还可以标注在类上。

@Import:给容器中快速导入一个组件

​ 给容器中注册组件的方式:

  • 适合自己写的类:包扫描+组件注解(@Controller/@Service等)

  • 适合导入的第三方包里的组件:@Bean

  • 快速给容器中导入:@Import,id默认是全类名

  • 使用Spring提供的FactoryBean

我们在bean中创建几个实体类(可以是个空类),我这里写的是Tools和Animal,然后在MainConfig2这个类中使用@Import注解,最后测试打印容器中的Bean名称。

@Import({Tools.class, Animal.class})

测试输出:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
... ...
mainConfig2
com.demo.bean.Tools
com.demo.bean.Animal
person
Tom
@Import:使用ImportSelector

​ ImportSelector接口是个导入选择器,返回需要导入的组建的全类名数组。

@Import({Tools.class, Animal.class,MyImportSelector.class})

MyImportSelector:

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

//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
   @Override
   //返回指就是导入到容器中的组件的全类名
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {

      return new String[]{"com.demo.bean.Hospital","com.demo.bean.Animal"};
   }
}

测试输出:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
... ...
mainConfig2
com.demo.bean.Tools
com.demo.bean.Animal
com.demo.bean.Hospital
person
Tom
@Import:使用ImportBeanDefinitionRegistrar

​ ImportBeanDefinationRegistrar也是一个接口,可以实现手动注册bean到容器中。

@Import({Tools.class, Animal.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})

自定义ImportBeanDefinationRegistrar:

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	/**
	  * @Description:
	  * @Param importingClassMetadata: 当前类的注解信息
	  * @Param registry: BeanDefination注册类
	  *         把所有需要添加到容器的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注册
	  * @return: void
	  **/

	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		//指定bean定义信息,如类型
		RootBeanDefinition beanDefinition=new RootBeanDefinition(cinema.class);
        //注册一个bean,指定bean名称
		registry.registerBeanDefinition("cinema",beanDefinition);
	}
}

测试输出:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
... ...
person
Tom
cinema
使用FactoryBean注册组件

​ 现在创建一个关于颜色的工厂Bean,ColorFactoryBean。

import org.springframework.beans.factory.FactoryBean;


public class ColorFactoryBean implements FactoryBean<Color> {

	@Override
	//返回一个Color对象,这个对象会添加到容器中
	public Color getObject() throws Exception {
		System.out.println("ColorFactoryBean...getObject..");
		return new Color();
	}

	@Override
	public Class<?> getObjectType() {
		return Color.class;
	}

	@Override
	public boolean isSingleton() {
		return false;
	}
}

不要忘记在MainConfig2.java中添加:

@Bean
	public ColorFactoryBean colorFactoryBean(){
		return new ColorFactoryBean();
}

测试:

	@Test
	public void test05(){
		AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig2.class);
		//获取容器中的组件名称
		String[] names=context.getBeanDefinitionNames();
		for(String name:names)
			System.out.println(name);

		Object bean=context.getBean("colorFactoryBean");
		System.out.println("bean的类型"+bean.getClass());
	}

输出结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
... ...
com.demo.bean.Hospital
person
Tom
colorFactoryBean
cinema
ColorFactoryBean...getObject..
bean的类型class com.demo.bean.Color

​ 工厂Bean获取的是调用getObject()创建的对象,是Color本身这个类型。如果想知道工厂Bean是什么类型的需要加前缀&。

Object bean=context.getBean("&colorFactoryBean");
System.out.println("bean的类型"+bean.getClass());

二、生命周期

  • 指定初始化和销毁方法:@Bean
  • 通过Bean实现InitializingBean和DisposableBean
  • JSR250:@PostConstruct依赖注入完成后进行初始化(对方法进行标注);@PreDestroy,bean移除之前执行,通知进行销毁工作
  • BeanPostProcessor:bean的后置处理器
@Bean指定初始化和销毁方法

​ 之前通过xml方式开发的时候,我们可以通过指定init-methoddestroy-method实现。那么如果想要通过注解要怎么做呢?

​ 首先我们创建一个Car实体类(无参构造方法+init+destroy)和MainConfigOfLifeCycle.java配置类。通过在@Bean注解中指定initMethod、destroyMethod来实现指定初始化和销毁方法。

@Configuration
public class MainConfigOfLifeCycle {
	@Bean(initMethod = "init",destroyMethod = "destroy")
	public Car car(){
		return new Car();
	}
}

测试:

public void test01(){
		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
		System.out.println("容器创建完成");
		annotationConfigApplicationContext.close();
	}

输出:

Car Constructor...
Car init....
容器创建完成
Car destroy...

​ 上面使用单实例演示的,大家可以通过@Scope设置prototype再来看看多实例有什么不同。

​ 在这里小结一下:

  • 创建:单实例,在容器启动的时候创建对象;多实例,在每次获取的时候创建对象。
  • 初始化:对象创建完成,并赋值后,调用初始化方法。
  • 销毁:单实例是在容器关闭的时候,多实例不会管理这个bean,容器不会调用销毁方法。
/*多实例*/	
public void test01(){
		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
		System.out.println("容器创建完成");
		Object bean = annotationConfigApplicationContext.getBean(Car.class);
		annotationConfigApplicationContext.close();
	}

测试输出:

容器创建完成
Car Constructor...
Car init....
InitializingBean和DisposableBean

​ 首先定义一个Cat类实现这两个接口,然后记得在MainConfigOfLifeCycle中注册该Bean。

public class Cat implements InitializingBean, DisposableBean {

	public Cat(){
		System.out.println("Cat Constructor...");
	}
	@Override
	public void destroy() throws Exception {
		System.out.println("Cat destroy...");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("Cat afterPropertiesSet...");

	}
}

测试输出:

Cat Constructor...
Cat afterPropertiesSet...
容器创建完成
Cat destroy...
@PostConstruct和@PreDestroy

public class Dog {
	public Dog(){
		System.out.println("Dog constructor...");
	}

	@PostConstruct
	public void init(){
		System.out.println("Dog...@PostConstruct...");
	}

	@PreDestroy
	public void destroy(){
		System.out.println("Dog...@@PreDestroy...");
	}
}

测试输出:

Cat Constructor...
Cat afterPropertiesSet...
Dog constructor...
Dog...@PostConstruct...
容器创建完成
Dog...@@PreDestroy...
Cat destroy...
@BeanPostProcessor:后置处理器

​ 该接口有两个方法:

  • postProcessBeforeInitialization:在初始化之前工作
  • postProcessAfterInitialization:在初始化之后工作

​ 定义一个后置处理器:MyBeanPostProcessor。

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessBeforeInitialization..."+bean);
		return bean;
	}
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessAfterInitialization..."+bean);
		return bean;
	}

}

测试输出:

postProcessBeforeInitialization...com.demo.config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$23e854c1@f8c1ddd
postProcessAfterInitialization...com.demo.config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$23e854c1@f8c1ddd
Cat Constructor...
postProcessBeforeInitialization...com.demo.bean.Cat@3901d134
Cat afterPropertiesSet...
postProcessAfterInitialization...com.demo.bean.Cat@3901d134
Dog constructor...
postProcessBeforeInitialization...com.demo.bean.Dog@65d6b83b
Dog...@PostConstruct...
postProcessAfterInitialization...com.demo.bean.Dog@65d6b83b
容器创建完成
Dog...@@PreDestroy...
Cat destroy...

三、属性赋值

@Value赋值
  • 基本数值:@Value(“张三”)

  • 使用spEL#{}:@Value("#{20-5}")

  • ${}:取出配置文件中的值

    先来看前两种:

//Person.java
@Value("张三")
private String name;
@Value("#{20+5}")
private Integer age;

//MainConfigOfPropertyValues.java
@Bean
	public Person person(){
		return new Person();
}

测试:

@Test
public void test01(){
    AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class);
    Person bean=context.getBean(Person.class);
    System.out.println(bean);
}

输出:

Person{name='张三', age=25}
@PropertySource加载外部配置文件

​ 在Person中加入一个新属性:sex,它的值通过加载外部配置文件Person.properties来实现赋值。

​ 以前是用xml配置时,我们需要写下列这样一行:

<context:property-placeholder location="classpath:person.properties">

​ 现在这个属性的来源通过@PropertySource实现,添加在MainConfigOfPropertyValues类上面。

//读取外部配置文件中的键值对保存到运行环境变量中
@PropertySource(value={"classpath:/person.properties"})

使用$赋值:

@Value("${person.sex}")
private String sex;

输出:

Person{name='张三', age=25, sex='female'}

四、自动装配

@Autowired @Qualifier @Primary

​ 如果一个组件需要用到另外一个组件,那么我们可以使用@Autowired注解进行中自动注入,该注解默认优先按照类型去容器中找对应的组件,如果有多个相同类型的组件,那么将属性的名称作为组件的id去容器中查找。我们可以使用@Qualifier(“bookDao”)指定需要装配的id,而不是使用属性名。

public class BookService {
	@Autowired
	private BookDao bookDao;
}

​ 在BookController、BookService分别自动装配BookService和BookDao。

扫描包:

@ComponentScan({"com.demo.service","com.demo.controller","com.demo.dao"})

测试:

@Test
	/*测试autowired*/
	public void test06(){
		AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
		BookService bean = context.getBean(BookService.class);
		BookDao bean1 = context.getBean(BookDao.class);
		System.out.println(bean1);
		System.out.println(bean);
	}

输出:

com.demo.dao.BookDao@6973bf95
BookService{bookDao=com.demo.dao.BookDao@6973bf95}

​ 注意:自动装配默认已经将属性赋值好,如果没有赋值那么就会报错,但是可以通过使用@Autowired(required=false)去解决。

​ @Primary可以在多个相同类型的Bean下使用,代表首选的Bean是哪个。

@Primary
@Bean("bookDao2")
public BookDao bookDao(){
    BookDao bookDao=new BookDao();
    bookDao.setLabel("2");
    return bookDao;
}

测试输出:

BookService [bookDao=BookDao [label=2]]
@Resource @Inject

​ 这两个注解是Java规范中的,@Autowired是Spring中的注解。

​ @Resource:默认按照组件名称进行装配,不支持@Primary功能和@Autowired(required=false)。

​ 使用@Inject注解还需要添加下列依赖:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

​ 该注解和@Autowired的功能相同,但是没有required=false功能。

方法、构造器位置的自动装配

​ @Autowired可以标注在构造器、参数、方法和属性上,在上一小节中仅仅展示了标注属性。

​ 新建一个Boss类,Car类型的变量car作为属性之一,我们添加setter和getter方法。

...
private Car car;
@Autowired
public void setCar(Car car){
    this.car=car;
}
...

​ 我们将@Autowired注解标注在了set方法上,那么Spring容器创建对象的时候就会调用该方法,完成赋值,方法使用的参数car、自定义类型的值会从ioc容器中获取,也就是说Boss类中的这个Car和ioc容器中的Car类型的Bean是同一个。

测试:

Boss boss=context.getBean(Boss.class);
System.out.println(boss);
Car car=context.getBean(Car.class);
System.out.println(car);

输出:

Boss [car=com.demo.bean.Car@4034c28c]
com.demo.beam.Car@4034c28c

标注在构造器上时,构造器要用的组件也是从容器中获取的。如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,因为效果和加上@Autowired是一样的。

@Autowired
public Boss(Car car){
    this.car=car;
    System.out.println("Boss的有参构造器。。。。");
}

放在参数位置:

public Boss(@Autowired Car car){
    this.car=car;
    System.out.println("Boss的有参构造器。。。。");
}

参考

尚硅谷雷丰阳老师Spring注解开发视频

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值