黑马程序员——JavaBean

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


1  JavaBean

1.1  JavaBean简介及定义规则

        JavaBean是对一类特殊Java类的统称,它们的特点在于,其方法的定义需要符合一定的规则:需为JavaBean类所代表事物的特定属性定义get/set方法,分别对应获取属性值和设置属性值的功能。这个属性并不一定是JavaBean类的私有成员变量,这个后面的内容中将进行说明。比如,将Peron类作为一个JavaBean,那么Person这类事物有一个age属性,那么就要进行如下定义:

代码1:

class Person{
	private int age;
 
	public int getAge(){
		return age;
	}
	public void setAge(int age){
		this.age = age;
	}
}
对于这两个方法的定义需要提醒大家几点:

        (1)  JavaBean首先是一种Java类,那么按照面向对象思想之一——封装的特点,类内部的属性通常要定义为私有,而对外提供用以对该属性进行操作的get/set方法,其访问权限必然要定义为公有。

        (2)  如果是手动编辑这两个方法,需要注意的是get方法不接受任何参数,返回值类型与属性类型相同;set方法必须接受一个参数,参数类型与属性类型相同,而返回值是void。

        (3)  方法名称的命名规则是,getXxx和setXxx,也就是get/set后接属性名,出现在方法名的属性名通常要将首字母大写。如果属性名称均由大写字母组成(多个单词的缩写,比如CPU),那么get/set后的属性名应保留原有的大小写特征。大家需要注意的是,虽然get/set方法通常代表JavaBean类对应事物的某个属性,但该JavaBean类源代码内部却不一定定义有对应的同名成员变量(就像上述Person那样,getAge代表age私有属性)。换句话说,get方法返回的不一定就是JavaBean类内部的某个私有属性,可以是通过某个方法计算出来并返回,set方法也是同理。当然,这两个方法内部的实现原理,并不是调用者所关心的,只要它们能够实现既定功能即可。所以,一对get/set方法,只能代表所属JavaBean类对应事物的某个特性,并不一定代表JavaBean类源代码内部的私有成员变量。

        那么我们说一个JavaBean,能够当做一个普通Java类来操作;但是反过来,一个普通Java类不一定就能称为JavaBean,这取决于该类内部是否为属性定义有get/set方法,而且这一对方法的名称要符合JavaBean的命名规则。

1.2  JavaBean的用途

        那么JavaBean到底是用来做什么的呢?在实际开发中,常常需要在两个模块之间对某个对象的信息进行传输,而传输方式就是将所需信息封装到一个对象中,那么在传输对象的同时,也就将封装在对象内的信息传输到了目标模块。JavaBean就是充当这个信息的载体,用于封装一个对象的属性信息,而封装的方式就是要符合JavaBean类的定义规则。由于JavaBean主要用于某个对象属性值(也就是信息)的封装与传输,因此JavaBean对象也被称为值对象(Value Object,简称VO)。

        由于JavaBean对象的上述用途,因此其内部出了属性相关的get/set方法以外,通常不会定义其他方法。

        以上内容对JavaBean的用途与定义方式进行了简单介绍,下面我们就来说明如何对JavaBean对象进行操作。

2  内省

2.1  内省简介

        当我们通过某种途径获取到一个JavaBean对象以后,该如何去操作它呢?可能有朋友会说,如果想要获取/设置封装在对象内的数据,直接调用get/set方法就可以了。但若要进行以上操作,需要看到该JavaBean类的源代码,我们才能知道内部封装有什么属性,以及对应的get/set方法,而这在程序开发阶段是不可能实现的,因为在开发时无法知道将来会操作什么JavaBean对象。

        那么解决这个问题的方法自然就是反射。通过反射可以动态地获取到一个类的所有信息,包括成员变量和成员方法,进而通过调用成员方法,对成员变量(也就是封装在JavaBean对象内部的信息)进行操作。为了更加方便的实现以上功能,免去手动编写反射代码的麻烦,Java标准类库java.beans包中提供了很多用于操作JavaBean对象的类,包括Instrospector、BeanInfo、PropertyDescriptor等,其中Instrospector是最为核心的工具类,英文翻译过来就是内省器,动词形式为instrospect,意思是内省,自省,反省。那么通过java.beans包中的类对JavaBean对象进行操作的过程就称为内省

2.2  内省操作演示

        下面我们以上述Person类为例,对JavaBean进行内省操作进行演示。为Person类再增加一个name属性,代码如下:

代码2:

public class Person {
	private int age;
	private String name;
      
	public Person(String name, int age){
              this.name = name;
              this.age = age;
	}
 
	public int getAge(){
		return age;
	}
	public void setAge(int age){
		this.age = age;
	}
	public String getName() {
		return name;
	}
       public void setName(String name) {
		this.name = name;
	}
}

        接下来我们来演示分别通过PropertyDescriptor和Instrospector来获取封装在JavaBean对象内的属性值。

(1)  PropertyDescriptor

        PropertyDescriptor的API文档描述为,该类描述JavaBean通过一对存储器方法导出的一个属性。该类的类名直译过来就是“属性描述器”,那么我们可以认为一个PropertyDescriptor对象就代表了一个JavaBean的属性。

构造方法:

        PropertyDescriptor(String propertyName, Class<?> beanClass):通过调用getFoo和setFoo存取方法,为符合标准Java约定的属性构造一个PropertyDescriptor。参数propertyName表示字符串形式的JavaBean属性名,beanClass表示某个JavaBean类的字节码对象。那么这一构造方法的作用就是根据一个JavaBean类字节码对象,使用反射技术获取其内部的所有成员信息,再根据指定的字符串形式属性名,将这一属性相关的信息封装为一个PropertyDescriptor对象,相关信息包括属性类型、获取/设置属性值的方法等等。

方法:

        public Class<?> getPropertyType():获得属性的Class对象。

        public Method getReadMethod():获得应该用于读取属性值的方法。该方法返回的即是此PropertyDescriptor对象代表属性的get方法。

        public Method getWriteMethod():获得应该用于写入属性值的方法。该方法返回的即是此PropertyDescriptor对象代表属性的set方法。

        下面我们就通过指定属性名称和JavaBean类的字节码对象,结合以上方法来对JavaBean对象进行内省操作进行演示。

代码3:

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
public class InstroSpectorTest {
	public static void main(String[] args) throws Exception {
		Person person = new Person("David", 25);
             
		String propertyName = "name";
		//获取Person对象name属性值
		System.out.println(getProperty(person,propertyName));
             
		//设置Person对象name属性值
		setProperty(person,propertyName, "Peter");
             
		//获取Person对象name属性值
		System.out.println(getProperty(person,propertyName));
	}
	//获取JavaBean对象指定属性的值
	private static Object getProperty(Object obj, String propertyName)
		throws IntrospectionException, IllegalAccessException,
		IllegalArgumentException, InvocationTargetException {
		//通过属性名和JavaBean对象创建PropertyDescriptor对象
		PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
		//获取代表属性get方法的Method对象
		Method methodGet = pd.getReadMethod();
		//获取Person对象的name属性值
		return methodGet.invoke(obj);
	}
	//设置JavaBean对象指定属性的值
	private static void setProperty(Object obj, String propertyName, Object value)
		throws IntrospectionException, IllegalAccessException,
		IllegalArgumentException, InvocationTargetException {
		//通过属性名和JavaBean对象创建PropertyDescriptor对象
		PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
		//获取代表属性set方法的Method对象
		Method methodSet = pd.getWriteMethod();
		//设置Person对象的name属性值为指定值
		methodSet.invoke(obj,value);
	}
}
以上代码的执行结果为:

David

Peter

以上代码中将获取/设置某JavaBean对象属性值的操作分别封装为了两个静态方法,可以更为清晰的显示操作步骤。

        从以上代码可知,通过PropertyDescriptor对象,即可非常方便的获取用于获取/设置属性的方法,进而对该属性进行操作。由此可知,PropertyDescriptor的作用就是通过一个JavaBean类的字节码对象,使用反射技术,获取该类内部的所有成员信息,再根据指定的字符串形式属性名,将该属性对应的所有信息封装为一个PropertyDescriptor对象,最终通过该属性对应的get/set方法对象对其进行操作。

(2) Instrospector

        相对于PropertyDescriptor类对指定属性的针对性操作,Introspector类对于JavaBean对象的操作更为通用一些,相对的也就更麻烦一些,因为它并不仅仅用于操作某一个指定属性,而是通过某个JavaBean对象的Class对象,获取到此类内部的所有信息(包括成员变量和成员方法),并将这些信息封装为一个称为BeanInfo的对象,而JavaBean对象中的属性和方法则以PropertyDescriptor和MethodDescriptor(与PropertyDescriptor类似,这里不进行详细介绍)对象数组的形式封装到BeanInfo中。若想对JavaBean的某个属性进行操作,则需要从PropertyDescriptor对象数组中获取执行属性对应的PropertyDescriptor对象即可。

        InstroSpector类的主要方法为,

        public static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException:在JavaBean上进行内省,了解其所有属性、公开的方法和事件。如果Java Bean的BeanInfo类以前已经被内省,则从BeanInfo缓存中检索BeanInfo类。

        BeanInfo(这是一个接口)实现类对象的主要方法为,

        public PropertyDescriptor[] getPropertyDescriptors():获取封装于BeanInfo对象中的代表相关JavaBean所有属性的PropertyDescriptor对象数组。

       下面我们演示如何使用IntroSpector类来对JavaBean对象进行内省操作。

代码4:

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
public class InstroSpectorTest 2 {
	public static void main(String[] args) throws Exception {
		Person person = new Person("David", 25);
             
		String propertyName = "name";
		//获取Person对象name属性值
		System.out.println(getProperty(person,propertyName));
             
		//设置Person对象name属性值
		setProperty(person, propertyName, "Peter");
             
		//获取Person对象name属性值
		System.out.println(getProperty(person,propertyName));
	}
	//获取JavaBean对象指定属性的值
	private static Object getProperty(Object obj, String propertyName)
		throws IntrospectionException, IllegalAccessException,
		IllegalArgumentException, InvocationTargetException {
		BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
		//获取存储有代表全部属性PropertyDescriptor对象的数组
		PropertyDescriptor[] propertyDescritors = beanInfo.getPropertyDescriptors();
		/*
		* 遍历PropertyDescriptor数组的同时,
		* 查找与指定属性名相同的PropertyDescriptor对象
		* */
		for(PropertyDescriptor propertyDescriptor : propertyDescritors){
			if(propertyName.equals(propertyDescriptor.getName())){
				//获取到用于获取指定属性值的方法对象,获取并返回属性值
				Method methodGet = propertyDescriptor.getReadMethod();
				return methodGet.invoke(obj);
			}
		}
             
		return null;
	}
	//设置JavaBean对象指定属性的值
	private static void setProperty(Object obj, String propertyName, Object value)
		throws IntrospectionException, IllegalAccessException,
		IllegalArgumentException, InvocationTargetException {
		BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
		//获取存储有代表全部属性PropertyDescriptor对象的数组
		PropertyDescriptor[] propertyDescritors = beanInfo.getPropertyDescriptors();
		/*
		* 遍历PropertyDescriptor数组的同时,
		* 查找与指定属性名相同的PropertyDescriptor对象
		* */
		for(PropertyDescriptor propertyDescriptor : propertyDescritors){
			if(propertyName.equals(propertyDescriptor.getName())){
				//获取到用于设置指定属性值的方法对象,将属性值设置为指定值
				Method methodSet = propertyDescriptor.getReadMethod();
				methodSet.invoke(obj,value);
			}
		}
	}           
}
以上代码的执行结果为:

David

Peter

       结果表明,通过Instrospector类同样可以完成对JavaBean的内省操作,但显然要更为通用和麻烦一些。由于无法指定需要操作的属性名称,因此只能在获取到代表所有属性PropertyDescriptor对象数组以后,通过遍历该数组查找数组中属性名称与指定属性名相同的PropertyDescriptor对象,进而通过该对象完成对指定属性的操作。

       不过,对比两种内省的方式,我们大概可以推断出,在手动创建PropertyDescriptor对象的过程,其底层实现原理其实就是在利用Instrospector类进行内省,通过遍历PropertyDescriptor数组,查找与指定属性名相同的PropertyDescriptor对象,进而对指定属性进行操作。

3  第三方类库

3.1  Apache-BeanUtils工具包简介

(1)  简介

       除了使用Java标准类库中的工具类以外,我们还可以使用第三方法类库提供的工具类对JavaBean对象进行操作,比如由Apache提供的beanutils工具包。beanutils工具包中的工具类提供了比Java标准类库java.beans包中工具类更为强大的功能,大大简化了操作JavaBean对象的步骤,提高了开发效率,而且这些类的源代码都是公开的,也就是所谓的开源,不仅能够更快的发现并修改其中的BUG,还有利于互相学习。

(2)  下载

       大家可以点击网址http://mirrors.hust.edu.cn/apache/commons/,下载其中beanutils、logging两个工具包,其中logging包是服务于beanuitls工具包的,因此也需要一同下载。下载以后是两个压缩包,解压缩以后的文件夹中包含有API文档,以及封装有各个工具类的jar包,其中最主要的就是文件名为commons-beanutils-1.9.2的“.jar”文件,只需将该文件导入到eclipse项目中即可使用其中的工具类。当然同时也需要分别导入另一个压缩包中名为commons-logging-1.2的jar包。

(3)  工具包的导入

       将所需jar包下载以后,需要将这些包含有工具类的jar包导入到eclipse项目中,才能像java标准类库中的类那样应用自如。导入方法为:首先在当前项目中新建一个文件夹,专门用于存放其他jar工具包。在eclipse项目视图中右键点击项目名称,依次点击New、Folder,在打开的“New Folder”窗口中选择当前项目,然后在Folder name栏中输入新建文件夹的名称,比如“Lib”。然后将上述两个“.jar”文件拷贝至Lib中,拷贝动作(Ctrl+C、Ctrl+V)可直接在eclipse中完成。接着在eclipse项目视图中右键点击其中一个jar包,依次点击Build Path——>Addto Build Path,随机就会在当前项目中自动创建了一个新的名为“Referenced Libraries”的文件夹,其中包含有导入进来的jar包,至此第三方类库jar包的导入操作完成。

3.2  BeanUtill工具类的应用

(1) BeanUtils工具类简介

       BeanUtils工具包中最为主要的就是BeanUtils工具类,对外提供了大量的静态方法,用于对JavaBean对象进行各种各样的操作。其API文档中的描述为,通过反射技术提供了若干用于从JavaBean对象获取属性的实用方法。

       主要方法有,

       public static String getProperty(Object bean, String name) thorws IllegalAccessException, InvocationTargetException, NoSuchMethodException:以字符串的形式返回指定JavaBean对象中指定属性的值。

       pubic static void setProperty(Object bean,String name, Object value) throws IllegalAccessExcepption, InvoacationTargetException:设置指定JavaBean中指定属性的值。参数value的类型应与被操作JavaBean对象中的属性类型相同。

       public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException:将源JavaBean中的属性值复制到目的JavaBean中相同名称的属性中。

       public static Map<String, String> describe(Object bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException:将指定JavaBean中所有提供了get方法的属性,按照键值对的形式,封装为一个Map集合并返回。

       public static void populate(Object bean,Map<String, ? extends Object> properties) throws IllegalAccessException, InvocationTargetException:将一个Map集合中所有的同名键的值复制到指定JavaBean中的属性。由此可知,describe和populate方法是互为逆过程。

       下面我们将演示如何应用BeanUtils工具类的上述方法。

(2)  对简单属性的操作

       我们同样将Person类对象作为JavaBean来操作,演示代码如下,

代码5:

import org.apache.commons.beanutils.BeanUtils;
 
public class InstroSpectorTest3 {
	public static void main(String[] args) throws Exception {
		Person person = new Person("David", 31);
             
		String age = BeanUtils.getProperty(person, "age");
		System.out.println(age);
             
		BeanUtils.setProperty(person,"age", 19);
             
		age = BeanUtils.getProperty(person, "age");
		System.out.println(age);
	}
}
代码的执行结果为:

31

19

通过以上代码同样实现了代码3和代码4的功能,并且相比较而言更为方便,代码量更少。

代码说明:

       注意到BeanUtils类的getProperty方法返回值为String类型,而不是指定属性在JavaBean中的原始类型。之所以这么设计的原因是,因为在实际应用中,用户在浏览器等软件中均是以字符串的形式输入各种信息,如果是程序员自己编写像BeanUtils那样的工具时,就需要将字符串形式的数据手动转换为对应的实际类型,再对其进行内省操作。那么BeanUtils工具类均在底层帮助我们实现了。

(3)  对复合属性的操作

       BeanUtils工具类不仅可以像以上代码那样对简单属性进行操作,还可以操作复合属性。所谓复合属性,就是属性的属性。比如,我们在Person类中再定义一个类型为Date(java.util包)类型的私有成员变量,而Date类自身具有一个名为time的属性,那么利用BeanUtils工具可以直接对复合属性time进行操作,代码如下,

代码6:

import java.util.Date;
 
public class Person {
	private int age;
	private String name;
      
	//定义一个Date类型的私有成员变量,作为一个新的属性
	private Date birthDay = new Date(10000);
 
	public Person(String name, int age){
		this.name = name;
		this.age = age;
	}
 
	public int getAge(){
		return age;
	}
	public void setAge(int age){
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	//为birthDay属性定义get/set方法
	public Date getBirthDay() {
		return birthDay;
	}
	public void setBirthDay(Date birthDay) {
		this.birthDay = birthDay;
	}
}
//测试类
import org.apache.commons.beanutils.BeanUtils;
 
public class IntroSpectorTest4 {
	public static void main(String[] args) throws Exception {
		Person person = new Person("George", 28);
             
		//获取复合属性time的值
		String birthDay = BeanUtils.getProperty(person, "birthDay.time");
		System.out.println("birthday= " + birthDay);
		//设置复合属性time的值
		BeanUtils.setProperty(person,"birthDay.time", "20000");
             
		birthDay = BeanUtils.getProperty(person, "birthDay.time");
		System.out.println("birthday= " + birthDay);
	}
}
代码执行结果为:

birthday = 10000

birthday = 20000

代码说明:

       在指定复合属性时,是按照“xxx.yyy”的形式,指定JavaBean对象中,某个属性的属性,则BeanUtils工具类就会自动去查找指定属性中的属性,并对其进行操作。如果有需要的话,可以按照同样的格式指定更深层次的属性,比如“xxx.yyy.zzz”。如果自定义该功能,可以想象是非常麻烦的,因此我们说BeanUtils是一个十分强大的工具类,为程序员提供了很多便利,提高了代码开发效率。

(4)  BeanUtils的其他方法

1)  复制属性

代码7:

import org.apache.commons.beanutils.BeanUtils;
 
public class IntroSpectorTest5{
	public static void main(String[] args) throws Exception {
		Person person1 = new Person("Kate", 22);
		Person person2 = new Person("Tom", 32);
             
		String name = BeanUtils.getProperty(person2, "name");
		String age = BeanUtils.getProperty(person2, "age");
		System.out.println("person2: " + name + " = " + age);
             
		//将person1的属性复制到person2中
		BeanUtils.copyProperties(person2,person1);
             
		name = BeanUtils.getProperty(person2, "name");
		age = BeanUtils.getProperty(person2, "age");
		System.out.println("person2: " + name + " = " + age);
	}
}
执行结果为:

person2 : Tom = 32

person2 : Kate = 22

代码说明:

       通过copyProperties方法就将源JavaBean中的所有同名属性,复制到了目的JavaBean中。该方法的应用前提是,源和目的JavaBean中必须包含有同名属性。BeanUtils类还提供了用于复制指定属性值的方法,请大家自行尝试。

2)  JavaBean属性与Map集合的转换

       JavaBean中属性名与属性值的关系非常类似于Map集合中的键值对,因此BeanUtils类共提供了用于实现JavaBean属性与Map集合之间进行转换的方法,describe与populate。

代码8:

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
 
import org.apache.commons.beanutils.BeanUtils;
 
public class IntroSpectorTest6 {
	public static void main(String[] args) throws Exception {
		Person person = new Person("Susan", 34);
             
		Map<String,String> properties = BeanUtils.describe(person);
		Set<String> keySet = properties.keySet();
		for(Iterator<String> it = keySet.iterator(); it.hasNext(); ){
			String key = it.next();
			String value = properties.get(key);
			System.out.println(key+ " = " + value);
		}
             
		//通过BeanUtils的方法为Map集合中的键设置值
		BeanUtils.setProperty(properties,"name", "Kate");
		BeanUtils.setProperty(properties,"age", "16");          
             
		BeanUtils.populate(person,properties);
             
		//System.out.println(BeanUtils.getProperty(person,"birthDay.time"));
		System.out.println(BeanUtils.getProperty(person,"name"));
		System.out.println(BeanUtils.getProperty(person,"class"));
		System.out.println(BeanUtils.getProperty(person,"age"));
	}
}
执行结果为:

name = Susan

class = class cn.tju.day1.Person

age = 34

Kate

class cn.tju.day1.Person

16

代码说明:

       (1)  通过以上代码,实现了Map集合与JavaBean属性之间的转换。与copyProperties方法一样,属性复制成功的前提是,保证Map集合中键的名称与JavaBean中属性名相同。

       (2) BeanUtils的setProperty方法还可以为Map集合中的键设置指定值,这正体现了Map集合键值对于JavaBean中属性名与属性值之间关系的相似性。

3.3  PropertyUtils工具类简介

       BeanUtils工具包中还提供了另一个用于操作JavaBean的工具类称为PropertyUtils,它的功能与BeanUtils基本类似,只不过在操作属性时,不再全部以字符串的形式,而是以JavaBean中定义的原始属性类型来进行操作。如下方法所示,

       public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException:返回指定JavaBean中指定属性的值,属性值的类型将一律转换为Object,而不会被转换为字符串。

       public static void setProperty(Object bean,String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException:将指定JavaBean中指定属性的值,设置为指定值。参数value的类型不再是字符串类型,而统一以Object对象的形式接收。

       下面将演示通过PropertyUtils工具类对JavaBean进行操作的方法。

代码9:

import org.apache.commons.beanutils.PropertyUtils;
 
public class IntroSpectorTest7 {
	public static void main(String[] args) throws Exception {
		Person person = new Person("George", 28);
             
		//String age = PropertyUtils.getProperty(person, "age");
		Object age = PropertyUtils.getProperty(person, "age");
		System.out.println("age= " + age);
             
		//PropertyUtils.setProperty(person,"age", "25");
		PropertyUtils.setProperty(person,"age", 25);
             
		age = PropertyUtils.getProperty(person, "age");
		System.out.println("age= " + age);
	}
}
执行结果为:

age = 28

age = 25

代码说明:

       以上代码中,两行被注释代码将无法通过编译。这是因为getProperty方法的返回值不再是BeanUtils的同名方法那样以字符串的形式进行接收,同时setProperty方法的指定属性值,也不再是以字符串的形式传递,而是以Object的形式接收。

       那么就要根据实际开发的需求,来对BeanUtils和PropertyUtils工具类进行选择:如果需要对属性值进行类型转换,则选择BeanUtils,否则可以选择使用PropertyUtils。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值