- 浏览: 315339 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
lzy.je:
期待FSF的!1985.10成立到现在GNU有多少大牛的项目数 ...
改变IT世界的11大Apache开源技术 -
dotaking:
7、8不了解
改变IT世界的11大Apache开源技术
CGlib简单介绍
CGlib概述:
cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
cglib封装了asm,可以在运行期动态生成新的class。
cglib用于AOP,jdk中的proxy必须基于接口,cglib却没有这个限制。
CGlib应用:
以一个实例在简单介绍下cglib的应用。
我们模拟一个虚拟的场景,关于信息的管理。
1)原始需求是任何人可以操作信息的create,update,delete,query操作。
InfoManager.java--封装对信息的操作
//模拟查询操作
publicvoidquery(){
System.out.println("query");
}
//模拟创建操作
publicvoidcreate(){
System.out.println("create");
}
//模拟更新操作
publicvoidupdate(){
System.out.println("update");
}
//模拟删除操作
publicvoiddelete(){
System.out.println("delete");
}
}
InfoManagerFactory.java--工厂类
privatestaticInfoManagermanger=newInfoManager();
/**
*创建原始的InfoManager
*
*@return
*/
publicstaticInfoManagergetInstance(){
returnmanger;
}
}
client.java--供客户端调用
publicstaticvoidmain(String[]args){
Clientc=newClient();
c.anyonecanManager();
}
/**
*模拟:没有任何权限要求,任何人都可以操作
*/
publicvoidanyonecanManager(){
System.out.println("anyonecandomanager");
InfoManagermanager=InfoManagerFactory.getInstance();
doCRUD(manager);
separatorLine();
}
/**
*对Info做增加/更新/删除/查询操作
*
*@parammanager
*/
privatevoiddoCRUD(InfoManagermanager){
manager.create();
manager.update();
manager.delete();
manager.query();
}
/**
*加一个分隔行,用于区分
*/
privatevoidseparatorLine(){
System.out.println("################################");
}
}
至此,没有涉及到cglib的内容,因为需求太简单了,但是接下来,需求发生了改变,要求:
2)只有一个叫“maurice”的用户登录,才允许对信息进行create,update,delete,query的操作。
怎么办?难道在每个方法前,都加上一个权限判断吗?这样重复逻辑太多了,于是乎想到了Proxy(代理模式),但是原先的InfoManager也没有实现接口,不能采用jdk的proxy。那么cglib在这边就要隆重登场。
一旦使用cgblig,只需要添加一个MethodInterceptor的类以及修改factory代码就可以实现这个需求。
AuthProxy.java--权限校验代理类
privateStringname;//会员登录名
publicAuthProxy(Stringname){
this.name=name;
}
/**
*权限校验,如果会员名为:maurice,则有权限做操作,否则提示没有权限
*/
@Override
publicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{
if(!"maurice".equals(this.name)){
System.out.println("AuthProxy:youhavenopermitstodomanager!");
returnnull;
}
returnproxy.invokeSuper(obj,args);
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
}
InfoManagerFactory.java--代码变动如下:
/**
*创建带有权限检验的InfoManager
*
*@paramauth
*@return
*/
publicstaticInfoManagergetAuthInstance(AuthProxyauth){
Enhancerenhancer=newEnhancer();
enhancer.setSuperclass(InfoManager.class);
enhancer.setCallback(auth);
return(InfoManager)enhancer.create();
}
}
client.java--代码修改如下
publicstaticvoidmain(String[]args){
Clientc=newClient();
c.haveNoAuthManager();
c.haveAuthManager();
}
/**
*模拟:登录会员没有权限
*/
publicvoidhaveNoAuthManager(){
System.out.println("theloginer'snameisnotmaurice,sohavenopermitsdomanager");
InfoManagernoAuthManager=InfoManagerFactory.getAuthInstance(newAuthProxy("maurice1"));
doCRUD(noAuthManager);
separatorLine();
}
/**
*模拟:登录会员有权限
*/
publicvoidhaveAuthManager(){
System.out.println("theloginer'snameismaurice,sohavepermitsdomanager");
InfoManagerauthManager=InfoManagerFactory.getAuthInstance(newAuthProxy("maurice"));
doCRUD(authManager);
separatorLine();
}
/**
*对Info做增加/更新/删除/查询操作
*
*@parammanager
*/
privatevoiddoCRUD(InfoManagermanager){
manager.create();
manager.update();
manager.delete();
manager.query();
}
/**
*加一个分隔行,用于区分
*/
privatevoidseparatorLine(){
System.out.println("################################");
}
}
执行下代码,发现这时client端中已经加上了权限校验。
同样是InfoManager,为什么这时能多了权限的判断呢?Factory中enhancer.create()返回的到底是什么对象呢?这个疑问将在第三部分CGlib中解释。
这边的代码,其实是介绍了cglib中的enhancer功能.
到这里,参照上面的代码,就可以使用cglib带来的aop功能了。但是为了更多介绍下cglib的功能,模拟需求再次发生变化:
3)由于query功能用户maurice才能使用,招来其他用户的强烈的抱怨,所以权限再次变更,只有create,update,delete,才需要权限保护,query任何人都可以使用。
怎么办?采用AuthProxy,使得InfoManager中的所有方法都被代理,加上了权限的判断。当然,最容易想到的办法,就是在AuthProxy的intercept的方法中再做下判断,如果代理的method是query,不需要权限验证。这么做,可以,但是一旦逻辑比较复杂的时候,intercept这个方法要做的事情会很多,逻辑会异常的复杂。
幸好,cglib还提供了CallbackFilter。使用CallbackFilter,可以明确表明,被代理的类(InfoManager)中不同的方法,被哪个拦截器(interceptor)拦截。
AuthProxyFilter.java
privatestaticfinalintAUTH_NEED=0;
privatestaticfinalintAUTH_NOT_NEED=1;
/**
*<pre>
*选择使用的proxy
*如果调用query函数,则使用第二个proxy
*否则,使用第一个proxy
*</pre>
*/
@Override
publicintaccept(Methodmethod){
if("query".equals(method.getName())){
returnAUTH_NOT_NEED;
}
returnAUTH_NEED;
}
}
这段代码什么意思?其中的accept方法的意思是说,如果代理的方法是query(),那么使用第二个拦截器去拦截,如果代理的方法不是query(),那么使用第一个拦截器去拦截。所以我们只要再写一个拦截器,不做权限校验就行了。(其实,cglib中的NoOp.INSTANCE就是一个空的拦截器,只要配置上这个就可以了。)
InfoManagerFactory.java--代码修改如下:(配置不同的拦截器和filter)
/**
*创建不同权限要求的InfoManager
*
*@paramauth
*@return
*/
publicstaticInfoManagergetSelectivityAuthInstance(AuthProxyauth){
Enhancerenhancer=newEnhancer();
enhancer.setSuperclass(InfoManager.class);
enhancer.setCallbacks(newCallback[]{auth,NoOp.INSTANCE});
enhancer.setCallbackFilter(newAuthProxyFilter());
return(InfoManager)enhancer.create();
}
}
记住:setCallbacks中的拦截器(interceptor)的顺序,一定要和CallbackFilter里面指定的顺序一致!!切忌。
Client.java
publicstaticvoidmain(String[]args){
Clientc=newClient();
c.selectivityAuthManager();
}
/**
*模拟:没有权限的会员,可以作查询操作
*/
publicvoidselectivityAuthManager(){
System.out.println("theloginer'snameisnotmaurice,sohavenopermitsdomanagerexceptdoqueryoperator");
InfoManagerauthManager=InfoManagerFactory.getSelectivityAuthInstance(newAuthProxy("maurice1"));
doCRUD(authManager);
separatorLine();
}
/**
*对Info做增加/更新/删除/查询操作
*
*@parammanager
*/
privatevoiddoCRUD(InfoManagermanager){
manager.create();
manager.update();
manager.delete();
manager.query();
}
/**
*加一个分隔行,用于区分
*/
privatevoidseparatorLine(){
System.out.println("################################");
}
}
此时,对于query的权限校验已经被去掉了。
通过一个模拟需求,简单介绍了cglib aop功能的使用。
CGlib应用非常广,在spring,hibernate等框架中,被大量的使用。
CGlib原理:
cglib神奇吗?其实一旦了解cglib enhancer的原理,一切就真相大白了。
刚才在第二部分中,有个疑问:enhancer.create()到底返回了什么对象?
其实在刚才的例子中,cglib在代码运行期,动态生成了InfoManager的子类,并且根据CallbackFilter的accept方法,覆写了InfoManager中的所有方法--去执行相应的MethodInterceptor的intercept方法。
有兴趣的朋友可以看看我反编译的InfoManager的子类,就可以很明白知道具体的实现了。(需要有耐心才可以)
InfoManager$$EnhancerByCGLIB$$de624598.jad
//Jadhomepage:http://www.geocities.com/kpdus/jad.html
//Decompileroptions:packimports(3)
//SourceFileName:<generated>
packagecn.eulic.codelab.cglib;
importjava.lang.reflect.Method;
importnet.sf.cglib.core.Signature;
importnet.sf.cglib.proxy.*;
//Referencedclassesofpackagecn.eulic.codelab.cglib:
//InfoManager
publicclassCGLIB.BIND_CALLBACKSextendsInfoManager
implementsFactory
{
staticvoidCGLIB$STATICHOOK1()
{
Classclass1;
ClassLoaderclassloader;
CGLIB$THREAD_CALLBACKS=newThreadLocal();
classloader=(class1=Class.forName("cn.eulic.codelab.cglib.InfoManager$$EnhancerByCGLIB$$de624598")).getClassLoader();
classloader;
CGLIB$emptyArgs=newObject[0];
CGLIB$delete$0$Proxy=MethodProxy.create(classloader,(CGLIB$delete$0$Method=Class.forName("cn.eulic.codelab.cglib.InfoManager").getDeclaredMethod("delete",newClass[0])).getDeclaringClass(),class1,"()V","delete","CGLIB$delete$0");
CGLIB$create$1$Proxy=MethodProxy.create(classloader,(CGLIB$create$1$Method=Class.forName("cn.eulic.codelab.cglib.InfoManager").getDeclaredMethod("create",newClass[0])).getDeclaringClass(),class1,"()V","create","CGLIB$create$1");
CGLIB$query$2$Proxy=MethodProxy.create(classloader,(CGLIB$query$2$Method=Class.forName("cn.eulic.codelab.cglib.InfoManager").getDeclaredMethod("query",newClass[0])).getDeclaringClass(),class1,"()V","query","CGLIB$query$2");
CGLIB$update$3$Proxy=MethodProxy.create(classloader,(CGLIB$update$3$Method=Class.forName("cn.eulic.codelab.cglib.InfoManager").getDeclaredMethod("update",newClass[0])).getDeclaringClass(),class1,"()V","update","CGLIB$update$3");
CGLIB$finalize$4$Proxy=MethodProxy.create(classloader,(CGLIB$finalize$4$Method=Class.forName("java.lang.Object").getDeclaredMethod("finalize",newClass[0])).getDeclaringClass(),class1,"()V","finalize","CGLIB$finalize$4");
CGLIB$hashCode$5$Proxy=MethodProxy.create(classloader,(CGLIB$hashCode$5$Method=Class.forName("java.lang.Object").getDeclaredMethod("hashCode",newClass[0])).getDeclaringClass(),class1,"()I","hashCode","CGLIB$hashCode$5");
CGLIB$clone$6$Proxy=MethodProxy.create(classloader,(CGLIB$clone$6$Method=Class.forName("java.lang.Object").getDeclaredMethod("clone",newClass[0])).getDeclaringClass(),class1,"()Ljava/lang/Object;","clone","CGLIB$clone$6");
CGLIB$equals$7$Proxy=MethodProxy.create(classloader,(CGLIB$equals$7$Method=Class.forName("java.lang.Object").getDeclaredMethod("equals",newClass[]{
Class.forName("java.lang.Object")
})).getDeclaringClass(),class1,"(Ljava/lang/Object;)Z","equals","CGLIB$equals$7");
CGLIB$toString$8$Proxy=MethodProxy.create(classloader,(CGLIB$toString$8$Method=Class.forName("java.lang.Object").getDeclaredMethod("toString",newClass[0])).getDeclaringClass(),class1,"()Ljava/lang/String;","toString","CGLIB$toString$8");
return;
}
finalvoidCGLIB$delete$0()
{
super.delete();
}
publicfinalvoiddelete()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;elsegoto_L1
_L1:
JVMINSTRpop;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVMINSTRdup;
JVMINSTRifnull37;
goto_L3_L4
_L3:
breakMISSING_BLOCK_LABEL_21;
_L4:
breakMISSING_BLOCK_LABEL_37;
this;
CGLIB$delete$0$Method;
CGLIB$emptyArgs;
CGLIB$delete$0$Proxy;
intercept();
return;
super.delete();
return;
}
finalvoidCGLIB$create$1()
{
super.create();
}
publicfinalvoidcreate()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;elsegoto_L1
_L1:
JVMINSTRpop;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVMINSTRdup;
JVMINSTRifnull37;
goto_L3_L4
_L3:
breakMISSING_BLOCK_LABEL_21;
_L4:
breakMISSING_BLOCK_LABEL_37;
this;
CGLIB$create$1$Method;
CGLIB$emptyArgs;
CGLIB$create$1$Proxy;
intercept();
return;
super.create();
return;
}
finalvoidCGLIB$query$2()
{
super.query();
}
publicfinalvoidquery()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;elsegoto_L1
_L1:
JVMINSTRpop;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVMINSTRdup;
JVMINSTRifnull37;
goto_L3_L4
_L3:
breakMISSING_BLOCK_LABEL_21;
_L4:
breakMISSING_BLOCK_LABEL_37;
this;
CGLIB$query$2$Method;
CGLIB$emptyArgs;
CGLIB$query$2$Proxy;
intercept();
return;
super.query();
return;
}
finalvoidCGLIB$update$3()
{
super.update();
}
publicfinalvoidupdate()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;elsegoto_L1
_L1:
JVMINSTRpop;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVMINSTRdup;
JVMINSTRifnull37;
goto_L3_L4
_L3:
breakMISSING_BLOCK_LABEL_21;
_L4:
breakMISSING_BLOCK_LABEL_37;
this;
CGLIB$update$3$Method;
CGLIB$emptyArgs;
CGLIB$update$3$Proxy;
intercept();
return;
super.update();
return;
}
finalvoidCGLIB$finalize$4()
throwsThrowable
{
super.finalize();
}
protectedfinalvoidfinalize()
throwsThrowable
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;elsegoto_L1
_L1:
JVMINSTRpop;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVMINSTRdup;
JVMINSTRifnull37;
goto_L3_L4
_L3:
breakMISSING_BLOCK_LABEL_21;
_L4:
breakMISSING_BLOCK_LABEL_37;
this;
CGLIB$finalize$4$Method;
CGLIB$emptyArgs;
CGLIB$finalize$4$Proxy;
intercept();
return;
super.finalize();
return;
}
finalintCGLIB$hashCode$5()
{
returnsuper.hashCode();
}
publicfinalinthashCode()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;elsegoto_L1
_L1:
JVMINSTRpop;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVMINSTRdup;
JVMINSTRifnull52;
goto_L3_L4
_L3:
this;
CGLIB$hashCode$5$Method;
CGLIB$emptyArgs;
CGLIB$hashCode$5$Proxy;
intercept();
JVMINSTRdup;
JVMINSTRifnonnull45;
goto_L5_L6
_L5:
JVMINSTRpop;
0;
goto_L7
_L6:
(Number);
intValue();
_L7:
return;
_L4:
returnsuper.hashCode();
}
finalObjectCGLIB$clone$6()
throwsCloneNotSupportedException
{
returnsuper.clone();
}
protectedfinalObjectclone()
throwsCloneNotSupportedException
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;elsegoto_L1
_L1:
JVMINSTRpop;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVMINSTRdup;
JVMINSTRifnull37;
goto_L3_L4
_L3:
this;
CGLIB$clone$6$Method;
CGLIB$emptyArgs;
CGLIB$clone$6$Proxy;
intercept();
return;
_L4:
returnsuper.clone();
}
finalbooleanCGLIB$equals$7(Objectobj)
{
returnsuper.equals(obj);
}
publicfinalbooleanequals(Objectobj)
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;elsegoto_L1
_L1:
JVMINSTRpop;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVMINSTRdup;
JVMINSTRifnull57;
goto_L3_L4
_L3:
this;
CGLIB$equals$7$Method;
newObject[]{
obj
};
CGLIB$equals$7$Proxy;
intercept();
JVMINSTRdup;
JVMINSTRifnonnull50;
goto_L5_L6
_L5:
JVMINSTRpop;
false;
goto_L7
_L6:
(Boolean);
booleanValue();
_L7:
return;
_L4:
returnsuper.equals(obj);
}
finalStringCGLIB$toString$8()
{
returnsuper.toString();
}
publicfinalStringtoString()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0!=null)goto_L2;<s
相关推荐
项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松copy复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(随意编程),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助 【资源内容】:项目具体内容可查看/点击本页面下方的*资源详情*,包含完整源码+工程文件+说明(若有)等。【若无VIP,此资源可私信获取】 【本人专注IT领域】:有任何使用问题欢迎随时与我联系,我会及时解答,第一时间为您提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【适合场景】:相关项目设计中,皆可应用在项目开发、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面中 可借鉴此优质项目实现复刻,也可基于此项目来扩展开发出更多功能 #注 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担 2. 部分字体及插图等来自网络,若是侵权请联系删除,本人不对所涉及的版权问题或内容负法律责任。收取的费用仅用于整理和收集资料耗费时间的酬劳 3. 积分资源不提供使用问题指导/解答
本文介绍了使用SpringBoot作为后端框架,Vue作为前端框架,MyBatis-Plus进行持久层开 。详细描述了系统测试的目的、功能测试案例,包括登录验证和用户管理,以及数据库设计。 前端:vue3 开发工具:IDEA 或者eclipse都支持 编程语言: java 框架:springboot/ssm都支持 jdk版本:jdk1.8以上均可 数据库: mysql 版本不限 数据库工具:Navicat/SQLyog都可以
该项目是Easy Log,一个基于Spring Boot 2.6.7开发的轻量级日志框架。它包含59个文件,其中48个Java源文件、2个Git忽略文件、1个LICENSE文件、1个Markdown文档、1个用于运行Maven的shell脚本、1个命令脚本、1个XML配置文件、1个工厂配置文件和1个YAML配置文件。Easy Log旨在简化日志记录流程,提供接口请求日志、错误日志,并支持通过编程方式记录操作日志,是业务开发中日志管理的得力助手。
全球恶嗪草酮行业总体规模、主要企业国内外市场占有率及排名(2024版).docx
“华为杯”第二十一届中国研究生数学建模竞赛e题.docx“华为杯”第二十一届中国研究生数学建模竞赛e题.docx“华为杯”第二十一届中国研究生数学建模竞赛e题.docx“华为杯”第二十一届中国研究生数学建模竞赛e题.docx
基于java的中国咖啡文化宣传网站的开题报告.docx
文章提出了一种用于超分辨率模型高效设计的新架构--边缘导向卷积块(Edge-oriented Convolution Block,简称ECB),并基于ECB进一步设计了面向移动设备的超轻量级超级图像上采样网络ECBSR,在多种基准数据集测试验证了其实时性能及效率的有效性。 适合人群包括从事计算机视觉研究尤其是移动设备上的高效神经网络应用的研究员和技术工程师。 应用场景主要是为了满足实际部署需要,即利用移动设备有限计算硬件资源完成图像视频实时高质量地进行低分辨率至高分辨率转换,同时兼顾推理速度和内存消耗。 本文提供了深入分析现有模型瓶颈以及解决思路与方法的具体细节,旨在提供对当前移动平台图像恢复解决方案有所助益的探讨和实验成果。
R__语言数据可视化学习_learning_R_data_visualization_Visualization
R语言实现的机器学习垃圾代码_R4ML
该资源详细解读可关注博主免费专栏《论文与完整程序》190号博文 MPC通过预测未来一段时间内系统的行为来优化控制输入,以达到特定的控制目标。其核心思想是利用系统的动态模型,通过求解优化问题,实时调整控制策略。 在实施过程中,MPC需要实时获取系统状态,并基于当前状态和预测模型计算未来的控制动作。这一过程涉及多个关键步骤:首先,建立一个准确的系统模型以捕捉动态行为;其次,设定控制目标和约束条件;最后,通过优化算法求解控制输入,这一过程需要高效的计算能力。 快速MPC的关键在于优化算法的效率,通常采用迭代方法或简化模型来加速计算,从而满足实时性要求。这使得MPC在工业自动化、机器人控制以及无人驾驶等领域得到广泛应用。 总之,基于在线优化的快速模型预测控制通过其预测能力和在线调整特性,实现了对复杂系统的高效控制,能够有效应对动态变化和外部扰动,提升系统的稳定性和性能。
java编程十大排序算法,包括冒泡,选择,插入,归并,快速,希尔,堆,桶,计数,基数排序_Top-Ten-Sorting-Algorithm
uafhaoafwoieajksdja
人工智能在教育领域的应用(任务)
变压器变频器配电柜电路控制原理图CAD施工图纸设备控制图消防泵一用一备控制原理图
该项目是基于JavaScript的GitBook电子书设计源码,共计90个文件,其中包括27个JavaScript源文件、14个CSS样式表文件、11个Markdown文档、9个JSON配置文件、5个PNG图片文件、3个HTML页面文件、3个GIF动画文件、2个NPM忽略文件、1个GitBook忽略文件、1个OTF字体文件,旨在创建一个结构清晰、易于阅读的电子书平台。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
“华为杯”第二十一届中国研究生数学建模竞赛fff题.docx“华为杯”第二十一届中国研究生数学建模竞赛fff题.docx“华为杯”第二十一届中国研究生数学建模竞赛fff题.docx“华为杯”第二十一届中国研究生数学建模竞赛fff题.docx“华为杯”第二十一届中国研究生数学建模竞赛fff题.docx
单片机流水灯实验报告.docx《word文档》单片机流水灯实验报告.docx《word文档》单片机流水灯实验报告.docx《word文档》单片机流水灯实验报告.docx《word文档》单片机流水灯实验报告.docx《word文档》
本文介绍了使用SpringBoot作为后端框架,Vue作为前端框架,MyBatis-Plus进行持久层开 。详细描述了系统测试的目的、功能测试案例,包括登录验证和用户管理,以及数据库设计。 前端:vue3 开发工具:IDEA 或者eclipse都支持 编程语言: java 框架:springboot/ssm都支持 jdk版本:jdk1.8以上均可 数据库: mysql 版本不限 数据库工具:Navicat/SQLyog都可以
R语言学习过程中的心路历程_R_learn