-
2018-10-14 23:06:30
转载自:https://www.cnblogs.com/fengrui-/p/6074381.html
一、简介
在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示。在SpringMVC 中提供了一个非常简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,然后使用@RequestMapping 和@RequestParam 等一些注解用以定义URL 请求和Controller 方法之间的映射,这样的Controller 就能被外界访问到。此外Controller 不会直接依赖于HttpServletRequest 和HttpServletResponse 等HttpServlet 对象,它们可以通过Controller 的方法参数灵活的获取到。为了先对Controller 有一个初步的印象,以下先定义一个简单的Controller :@Controller public class MyController { @RequestMapping ( "/showView" ) public ModelAndView showView() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName( "viewName" ); modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " ); return modelAndView; } }
在上面的示例中,@Controller 是标记在类MyController 上面的,所以类MyController 就是一个SpringMVC Controller 对象了,然后使用@RequestMapping(“/showView”) 标记在Controller 方法上,表示当请求/showView.do 的时候访问的是MyController 的showView 方法,该方法返回了一个包括Model 和View 的ModelAndView 对象。
二、使用 @Controller 定义一个 Controller 控制器
@Controller 用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器,这个接下来就会讲到。单单使用@Controller 标记在一个类上还不能真正意义上的说它就是SpringMVC 的一个控制器类,因为这个时候Spring 还不认识它。那么要如何做Spring 才能认识它呢?这个时候就需要我们把这个控制器类交给Spring 来管理。拿MyController 来举一个例子
@Controller public class MyController { @RequestMapping ( "/showView" ) public ModelAndView showView() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName( "viewName" ); modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " ); return modelAndView; } }
这个时候有两种方式可以把MyController 交给Spring 管理,好让它能够识别我们标记的@Controller 。
第一种方式是在SpringMVC 的配置文件中定义MyController 的bean 对象。
<bean class="com.host.app.web.controller.MyController"/>
第二种方式是在SpringMVC 的配置文件中告诉Spring 该到哪里去找标记为@Controller 的Controller 控制器。
< context:component-scan base-package = "com.host.app.web.controller" > < context:exclude-filter type = "annotation" expression = "org.springframework.stereotype.Service" /> </ context:component-scan >
注:上面 context:exclude-filter 标注的是不扫描 @Service 标注的类
三、使用 @RequestMapping 来映射 Request 请求与处理器
可以使用@RequestMapping 来映射URL 到控制器类,或者是到Controller 控制器的处理方法上。当@RequestMapping 标记在Controller 类上的时候,里面使用@RequestMapping 标记的方法的请求地址都是相对于类上的@RequestMapping 而言的;当Controller 类上没有标记@RequestMapping 注解时,方法上的@RequestMapping 都是绝对路径。这种绝对路径和相对路径所组合成的最终路径都是相对于根路径“/ ”而言的。@Controller public class MyController { @RequestMapping ( "/showView" ) public ModelAndView showView() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName( "viewName" ); modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " ); return modelAndView; } }
在这个控制器中,因为MyController 没有被@RequestMapping 标记,所以当需要访问到里面使用了@RequestMapping 标记的showView 方法时,就是使用的绝对路径/showView.do 请求就可以了。
@Controller @RequestMapping ( "/test" ) public class MyController { @RequestMapping ( "/showView" ) public ModelAndView showView() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName( "viewName" ); modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " ); return modelAndView; } }
这种情况是在控制器上加了@RequestMapping 注解,所以当需要访问到里面使用了@RequestMapping 标记的方法showView() 的时候就需要使用showView 方法上@RequestMapping 相对于控制器MyController上@RequestMapping 的地址,即/test/showView.do 。
(一)使用 URI 模板
URI 模板就是在URI 中给定一个变量,然后在映射的时候动态的给该变量赋值。如URI 模板http://localhost/app/{variable1}/index.html ,这个模板里面包含一个变量variable1 ,那么当我们请求http://localhost/app/hello/index.html 的时候,该URL 就跟模板相匹配,只是把模板中的variable1 用hello 来取代。在SpringMVC 中,这种取代模板中定义的变量的值也可以给处理器方法使用,这样我们就可以非常方便的实现URL 的RestFul 风格。这个变量在SpringMVC 中是使用@PathVariable 来标记的。在SpringMVC 中,我们可以使用@PathVariable 来标记一个Controller 的处理方法参数,表示该参数的值将使用URI 模板中对应的变量的值来赋值。
@Controller @RequestMapping ( "/test/{variable1}" ) public class MyController { @RequestMapping ( "/showView/{variable2}" ) public ModelAndView showView( @PathVariable String variable1, @PathVariable ( "variable2" ) int variable2) { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName( "viewName" ); modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " ); return modelAndView; } }
在上面的代码中我们定义了两个URI 变量,一个是控制器类上的variable1 ,一个是showView 方法上的variable2,然后在showView 方法的参数里面使用@PathVariable 标记使用了这两个变量。所以当我们使用/test/hello/showView/2.do 来请求的时候就可以访问到MyController 的showView 方法,这个时候variable1 就被赋予值hello ,variable2 就被赋予值2 ,然后我们在showView 方法参数里面标注了参数variable1 和variable2 是来自访问路径的path 变量,这样方法参数variable1 和variable2 就被分别赋予hello 和2 。方法参数variable1 是定义为String 类型,variable2 是定义为int 类型,像这种简单类型在进行赋值的时候Spring 是会帮我们自动转换的
在上面的代码中我们可以看到在标记variable1 为path 变量的时候我们使用的是@PathVariable ,而在标记variable2 的时候使用的是@PathVariable(“variable2”) 。这两者有什么区别呢?第一种情况就默认去URI 模板中找跟参数名相同的变量,但是这种情况只有在使用debug 模式进行编译的时候才可以,而第二种情况是明确规定使用的就是URI 模板中的variable2 变量。当不是使用debug 模式进行编译,或者是所需要使用的变量名跟参数名不相同的时候,就要使用第二种方式明确指出使用的是URI 模板中的哪个变量。
除了在请求路径中使用URI 模板,定义变量之外,@RequestMapping 中还支持通配符“* ”。如下面的代码我就可以使用/myTest/whatever/wildcard.do 访问到Controller 的testWildcard 方法。
@Controller @RequestMapping ( "/myTest" ) public class MyController { @RequestMapping ( "*/wildcard" ) public String testWildcard() { System. out .println( "wildcard------------" ); return "wildcard" ; } }
(二)使用 @RequestParam 绑定 HttpServletRequest 请求参数到控制器方法参数
@RequestMapping ( "requestParam" ) public String testRequestParam( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) { return "requestParam" ; }
在上面代码中利用@RequestParam 从HttpServletRequest 中绑定了参数name 到控制器方法参数name ,绑定了参数age 到控制器方法参数age 。值得注意的是和@PathVariable 一样,当你没有明确指定从request 中取哪个参数时,Spring 在代码是debug 编译的情况下会默认取更方法参数同名的参数,如果不是debug 编译的就会报错。此外,当需要从request 中绑定的参数和方法的参数名不相同的时候,也需要在@RequestParam 中明确指出是要绑定哪个参数。在上面的代码中如果我访问/requestParam.do?name=hello&age=1 则Spring 将会把request请求参数name 的值hello 赋给对应的处理方法参数name ,把参数age 的值1 赋给对应的处理方法参数age 。
在@RequestParam 中除了指定绑定哪个参数的属性value 之外,还有一个属性required ,它表示所指定的参数是否必须在request 属性中存在,默认是true ,表示必须存在,当不存在时就会报错。在上面代码中我们指定了参数name 的required 的属性为false ,而没有指定age 的required 属性,这时候如果我们访问/requestParam.do而没有传递参数的时候,系统就会抛出异常,因为age 参数是必须存在的,而我们没有指定。而如果我们访问/requestParam.do?age=1 的时候就可以正常访问,因为我们传递了必须的参数age ,而参数name 是非必须的,不传递也可以。
更多相关内容 -
Spring Boot中Controller间的调用
2022-01-20 10:26:26假定两个Controller都在同一个工程中。 如果有比较合理的分层设计,这样的需求应该是非常罕见的。因为大部分情况下,调用应该都是限于对业务逻辑层或者数据库层,并不会涉及Controller之间的调用。 本文是讨论... -
GrblController安装与使用教程
2018-07-16 21:05:04本文介绍了Grbl Controller安装与使用教程,是使用Grbl Controller的入门基础必读文件。 -
USB-Serial Controller D USB转串口驱动 免费版
2018-12-17 15:28:18USB-Serial Controller D USB转串口驱动 免费版,亲测可以用 -
amd sata controller(AMD SATA控制器)
2018-03-06 19:57:23Windows10系统不能识别新增光驱位机械硬盘的解决方法,安装amd_sata_controller驱动程序 。 右键点击系统桌面左下角的【开始】,在开始菜单中点击【设备管理器(M)】 -
SSM框架下基本的mapper,dao,service,controller等相关文件的整理
2017-01-04 13:00:07SSM框架下基本的mapper,dao,service,controller等相关文件的整理 -
Realtek PCIe GBE Family Controller 网卡驱动
2015-12-25 17:20:52Realtek PCIe GBE Family Controller 网卡驱动 for Linux -
usb 2.0 video capture controller
2015-06-02 18:32:19闭路摄像头驱动usb 2.0 video capture controller -
急!java:controller调用其他controller中的接口怎么实现
2021-03-10 15:13:49<code>Test1Controller中下面这个接口 @ApiOperation(value = "测试接口", notes = "测试接口", response = BaseResult.class, responseContainer = "Map") @... -
SpringBoot教程(五) | SpringBoot中Controller用法及传参
2022-01-19 09:48:19SpringBoot整合SpringMvc其实千面一直讲的都是。只需要我们在pom文件中引入 web的starter就可以了,然后我们就可以正常使用springMvc中的功能了。所以本篇文章可能更多的是回顾,回顾一下...重点讲讲Controller的传参SpringBoot整合SpringMvc其实千面一直讲的都是。只需要我们在pom文件中引入 web的starter就可以了,然后我们就可以正常使用springMvc中的功能了。所以本篇文章可能更多的是回顾,回顾一下springMVC中的一些常用的功能。
按照正常的流程,我们应该先讲一讲怎么配置视图解析器,但是已经提到了,现在都是前后端分离的项目,后端无需配置页面跳转,只需要返回数据即可,所以这一部分也没必要再讲了。那咱么就介绍下Controller中的常用写法。
上一次我们介绍了 @RestController 注解,这个注解会把所有的返回结果以json(json其实不太准确,应该是可序列化对象,大部分是json形式)的形式进行返回。我们今天来举个例子。
5.1关于@RestController注解
Controller代码:
@RestController public class SecondController { @RequestMapping("/second") public User second(){ User u = new User("张胜男", 30,"女"); return u; } }
这里返回了一个User类。 User类代码如下:
package com.lsqingfeng.springboot.vo; /** * @className: User * @description: * @author: sh.Liu * @date: 2022-01-12 11:25 */ public class User { private String name; private Integer age; private String gender; public User(String name, Integer age, String gender) { this.name = name; this.age = age; this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } }
访问:http://localhost:9090/second 得到结果:
看到了返回的就是json类型的数据。这就是 @RestController的作用。如果不用@RestController,我们在方法上加上@ResponseBody是同样的效果。 @RestController注解就是@Controller和@ResponseBody 的组合。
5.2 关于@RequestMapping注解
@RequestMapping注解主要用于标识该Controller的请求地址路径。比如上面路径中的@RequestMapping("/second") 就代表通过 /second可以访问到这个接口。这个注解既可以放在类上,也可以放到方法上,如果类上也有这个注解,那么类中所有的controller在访问的时候,都要带上类上的路径才可以访问到。比如:
@RestController @RequestMappin("/test") public class SecondController { @RequestMapping("/second") public User second(){ User u = new User("张胜男", 30,"女"); return u; } @RequestMapping("/third") public User third(){ User u = new User("张胜", 50, "男"); return u; } }
比如上面的Controller, 那么我们访问的路径就变成了
ip:port/test/second ip:port/test/third
并且这也是现在最常用的方式,相当于类上的注解里的url作为一个大的分类(一般代表一个模块),方法是模块中的各个功能。
同时还要注意,如果方法上面使用了@RequestMapping 注解来进行标记,那么其实并没有限定这个方法用Http的哪种请求方式来进行访问,所以我们可以用Get请求,也可以用post请求。当然除了这些还有一下不太常用的Delete,put,optio等请求方式。比如我们最上边那个例子:
Get请求访问:
Post请求访问:
Delete请求访问:
但是一般情况下,我们都会限定这个接口只能一某一种请求方式访问,如何限制呢?
方法一: 通过RequestMapping中的参数
@RestController public class SecondController { @RequestMapping(value = "/second", method = RequestMethod.GET) public User second(){ User u = new User("张胜男", 30,"女"); return u; } }
这时候就只能用Get请求来访问了。
其他请求方式会报错.
方法二: 使用限定请求方式的注解:
如果限定Get请求方式, 用@GetMapping 代替 @RequestMapping\
如果限定Post请求方式, 用@PostMapping 代替 @RequestMapping
比如限定Post请求方式:
@RestController public class SecondController { @PostMapping("second") public User second(){ User u = new User("张胜男", 30,"女"); return u; } }
我们可以看下 @PostMapping注解的实现方式,其实跟方式一是一样的。
5.3关于Controller中传参
Get请求传参:
Get请求的参数传递都是通过在url后面拼接来进行传参的, 比如我们传一个name 和age 。url的写法如下:
localhost:9090/third?name=zhangsan&age=20
在请求的路径?后面表示参数,多个参数之间用 & 进行连接。 那么在Controller中如何接收Get请求传递过来的参数呢。方式很多,我们就说两种最常用的
- 直接在Controller方法的参数中,用相同的变量名称接收参数。以上面的请求为例
@GetMapping("third") public String third(String name, Integer age){ System.out.println(name); System.out.println(age); return "success"; }
成功获取参数:
- 如果参数太多,用上面的方式来写,会导致方法中的参数过多,看起来很乱。所以我们也可以使用一个javaBean来接收。前期就是javaBean中的属性名要和参数名相同,同时要有get和set方法。
@GetMapping("getParam") public User getParam(User user){ System.out.println(user); // 将接收到的参数直接返回 return user; }
User类,再给一下:
package com.lsqingfeng.springboot.vo; /** * @className: User * @description: * @author: sh.Liu * @date: 2022-01-12 11:25 */ public class User { private String name; private Integer age; private String gender; public User(String name, Integer age, String gender) { this.name = name; this.age = age; this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "User{" + "name='" + name + ''' + ", age=" + age + ", gender='" + gender + ''' + '}'; } }
访问: http://localhost:9090/getParam?name=zhangsan&age=20
gender没有传值,所以是null, 参数接收成功:
Post请求传参
Post请求传参的方式,常用的有两种,一中是通过form表单进行传参(就是html中的form),还有一种是通过json的形式传参。目前来说用json的比较多。
- Form传参
使用form传参,我们的Controller再接收的时候和上面一样可以直接接收上代码。
@PostMapping("postForm") public User postForm(String name, Integer age){ User u = new User(name, age, null); return u; }
post请求需要通过postman来模拟了,注意form传参的位置,要在body的form-data里
如果参数过多,我们也是可以通过一个javaBean来进行接收的。
@PostMapping("postForm2") public User postForm2(User user){ return user; }
2. Json 传参
通过json传递的参数,content-type一般为: application/json
我们在接收参数的时候要通过一个新的注解 @RequestBody 来进行标识。
// 注意,User中一定要有无参构造方法 @PostMapping("postJson") public User postJson(@RequestBody User user){ return user; }
通过json方式请求,看是否成功获取。
3. 关于@RequestParam注解
还有一种方式,这种方式不是很常用,但有的时候,跟三方接口对接的时候,不排除他们使用这种方式。
就是针对content-type是: application/x-www-form-urlcoded (Http协议中,如果不指定Content-Type,则默认传递的参数就是application/x-www-form-urlencoded类型)这是我在网上的资料中找到的,也验证了,如下:
@PostMapping("postWWWForm") public User postWWWForm(@RequestParam String name, @RequestParam Integer age){ return new User(name, age, null); }
后来我把@RequestParam 注解去掉了,发现居然也可以接收到参数。其实这个注解就是用来接收普通参数的一个注解。正常情况下应该是可以省略的。什么时候不能省略呢,就是有时候这个参数是必填项,就是必须要传,那么我们可以在这个注解中标识是否必传和默认值。
@RequestParam(value="id",required =false,defaultValue ="10086") Integer id,
默认是true.
好了关于SpringBoot中Controller的传参方式我们就介绍这么多。如有错误也欢迎大家交流指正。
-
k8s自定义controller三部曲之三:编写controller代码
2019-04-01 00:15:25本文是《k8s自定义controller三部曲》的终篇,编写controller的代码,通过监听API对象的增删改变化做出业务方面的响应本文是《k8s自定义controller三部曲》的终篇,前面的章节中,我们创建了CRD,再通过自动生成代码的工具将controller所需的informer、client等依赖全部准备好,到了本章,就该编写controller的代码了,也就是说,现在已经能监听到Student对象的增删改等事件,接下来就是根据这些事件来做不同的事情,满足个性化的业务需求;
三部曲所有文章链接
- 《k8s自定义controller三部曲之一:创建CRD(Custom Resource Definition)》;
- 《k8s自定义controller三部曲之二:自动生成代码》;
- 《k8s自定义controller三部曲之三:编写controller代码》;
源码下载
接下来详细讲述应用的编码过程,如果您不想自己写代码,也可以在GitHub下载完整的应用源码,地址和链接信息如下表所示:
名称 链接 备注 项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页 git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议 git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议 这个git项目中有多个文件夹,本章源码在k8s_customize_controller这个文件夹下,如下图红框所示:
开始实战
- 回顾一下,上一章通过自动代码生成工具生成代码后,源码目录的内容如下:
[root@golang k8s_customize_controller]# tree . └── pkg ├── apis │ └── bolingcavalry │ ├── register.go │ └── v1 │ ├── doc.go │ ├── register.go │ ├── types.go │ └── zz_generated.deepcopy.go └── client ├── clientset │ └── versioned │ ├── clientset.go │ ├── doc.go │ ├── fake │ │ ├── clientset_generated.go │ │ ├── doc.go │ │ └── register.go │ ├── scheme │ │ ├── doc.go │ │ └── register.go │ └── typed │ └── bolingcavalry │ └── v1 │ ├── bolingcavalry_client.go │ ├── doc.go │ ├── fake │ │ ├── doc.go │ │ ├── fake_bolingcavalry_client.go │ │ └── fake_student.go │ ├── generated_expansion.go │ └── student.go ├── informers │ └── externalversions │ ├── bolingcavalry │ │ ├── interface.go │ │ └── v1 │ │ ├── interface.go │ │ └── student.go │ ├── factory.go │ ├── generic.go │ └── internalinterfaces │ └── factory_interfaces.go └── listers └── bolingcavalry └── v1 ├── expansion_generated.go └── student.go 21 directories, 27 files
- 本章要编写的第一个go文件就是controller.go,在k8s_customize_controller目录下创建controller.go,代码内容如下:
package main import ( "fmt" "time" "github.com/golang/glog" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" bolingcavalryv1 "github.com/zq2599/k8s-controller-custom-resource/pkg/apis/bolingcavalry/v1" clientset "github.com/zq2599/k8s-controller-custom-resource/pkg/client/clientset/versioned" studentscheme "github.com/zq2599/k8s-controller-custom-resource/pkg/client/clientset/versioned/scheme" informers "github.com/zq2599/k8s-controller-custom-resource/pkg/client/informers/externalversions/bolingcavalry/v1" listers "github.com/zq2599/k8s-controller-custom-resource/pkg/client/listers/bolingcavalry/v1" ) const controllerAgentName = "student-controller" const ( SuccessSynced = "Synced" MessageResourceSynced = "Student synced successfully" ) // Controller is the controller implementation for Student resources type Controller struct { // kubeclientset is a standard kubernetes clientset kubeclientset kubernetes.Interface // studentclientset is a clientset for our own API group studentclientset clientset.Interface studentsLister listers.StudentLister studentsSynced cache.InformerSynced workqueue workqueue.RateLimitingInterface recorder record.EventRecorder } // NewController returns a new student controller func NewController( kubeclientset kubernetes.Interface, studentclientset clientset.Interface, studentInformer informers.StudentInformer) *Controller { utilruntime.Must(studentscheme.AddToScheme(scheme.Scheme)) glog.V(4).Info("Creating event broadcaster") eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")}) recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName}) controller := &Controller{ kubeclientset: kubeclientset, studentclientset: studentclientset, studentsLister: studentInformer.Lister(), studentsSynced: studentInformer.Informer().HasSynced, workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Students"), recorder: recorder, } glog.Info("Setting up event handlers") // Set up an event handler for when Student resources change studentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: controller.enqueueStudent, UpdateFunc: func(old, new interface{}) { oldStudent := old.(*bolingcavalryv1.Student) newStudent := new.(*bolingcavalryv1.Student) if oldStudent.ResourceVersion == newStudent.ResourceVersion { //版本一致,就表示没有实际更新的操作,立即返回 return } controller.enqueueStudent(new) }, DeleteFunc: controller.enqueueStudentForDelete, }) return controller } //在此处开始controller的业务 func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { defer runtime.HandleCrash() defer c.workqueue.ShutDown() glog.Info("开始controller业务,开始一次缓存数据同步") if ok := cache.WaitForCacheSync(stopCh, c.studentsSynced); !ok { return fmt.Errorf("failed to wait for caches to sync") } glog.Info("worker启动") for i := 0; i < threadiness; i++ { go wait.Until(c.runWorker, time.Second, stopCh) } glog.Info("worker已经启动") <-stopCh glog.Info("worker已经结束") return nil } func (c *Controller) runWorker() { for c.processNextWorkItem() { } } // 取数据处理 func (c *Controller) processNextWorkItem() bool { obj, shutdown := c.workqueue.Get() if shutdown { return false } // We wrap this block in a func so we can defer c.workqueue.Done. err := func(obj interface{}) error { defer c.workqueue.Done(obj) var key string var ok bool if key, ok = obj.(string); !ok { c.workqueue.Forget(obj) runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj)) return nil } // 在syncHandler中处理业务 if err := c.syncHandler(key); err != nil { return fmt.Errorf("error syncing '%s': %s", key, err.Error()) } c.workqueue.Forget(obj) glog.Infof("Successfully synced '%s'", key) return nil }(obj) if err != nil { runtime.HandleError(err) return true } return true } // 处理 func (c *Controller) syncHandler(key string) error { // Convert the namespace/name string into a distinct namespace and name namespace, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { runtime.HandleError(fmt.Errorf("invalid resource key: %s", key)) return nil } // 从缓存中取对象 student, err := c.studentsLister.Students(namespace).Get(name) if err != nil { // 如果Student对象被删除了,就会走到这里,所以应该在这里加入执行 if errors.IsNotFound(err) { glog.Infof("Student对象被删除,请在这里执行实际的删除业务: %s/%s ...", namespace, name) return nil } runtime.HandleError(fmt.Errorf("failed to list student by: %s/%s", namespace, name)) return err } glog.Infof("这里是student对象的期望状态: %#v ...", student) glog.Infof("实际状态是从业务层面得到的,此处应该去的实际状态,与期望状态做对比,并根据差异做出响应(新增或者删除)") c.recorder.Event(student, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced) return nil } // 数据先放入缓存,再入队列 func (c *Controller) enqueueStudent(obj interface{}) { var key string var err error // 将对象放入缓存 if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { runtime.HandleError(err) return } // 将key放入队列 c.workqueue.AddRateLimited(key) } // 删除操作 func (c *Controller) enqueueStudentForDelete(obj interface{}) { var key string var err error // 从缓存中删除指定对象 key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) return } //再将key放入队列 c.workqueue.AddRateLimited(key) }
上述代码有以下几处关键点:
a. 创建controller的NewController方法中,定义了收到Student对象的增删改消息时的具体处理逻辑,除了同步本地缓存,就是将该对象的key放入消息中;
b. 实际处理消息的方法是syncHandler,这里面可以添加实际的业务代码,来响应Student对象的增删改情况,达到业务目的;
3. 接下来可以写main.go了,不过在此之前把处理系统信号量的辅助类先写好,然后在main.go中会用到(处理例如ctrl+c的退出),在$GOPATH/src/k8s_customize_controller/pkg目录下新建目录signals;
4. 在signals目录下新建文件signal_posix.go:// +build !windows package signals import ( "os" "syscall" ) var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
- 在signals目录下新建文件signal_posix.go:
package signals import ( "os" ) var shutdownSignals = []os.Signal{os.Interrupt}
- 在signals目录下新建文件signal.go:
package signals import ( "os" "os/signal" ) var onlyOneSignalHandler = make(chan struct{}) func SetupSignalHandler() (stopCh <-chan struct{}) { close(onlyOneSignalHandler) // panics when called twice stop := make(chan struct{}) c := make(chan os.Signal, 2) signal.Notify(c, shutdownSignals...) go func() { <-c close(stop) <-c os.Exit(1) // second signal. Exit directly. }() return stop }
- 接下来可以编写main.go了,在k8s_customize_controller目录下创建main.go文件,内容如下,关键位置已经加了注释,就不再赘述了:
package main import ( "flag" "time" "github.com/golang/glog" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" clientset "k8s_customize_controller/pkg/client/clientset/versioned" informers "k8s_customize_controller/pkg/client/informers/externalversions" "k8s_customize_controller/pkg/signals" ) var ( masterURL string kubeconfig string ) func main() { flag.Parse() // 处理信号量 stopCh := signals.SetupSignalHandler() // 处理入参 cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig) if err != nil { glog.Fatalf("Error building kubeconfig: %s", err.Error()) } kubeClient, err := kubernetes.NewForConfig(cfg) if err != nil { glog.Fatalf("Error building kubernetes clientset: %s", err.Error()) } studentClient, err := clientset.NewForConfig(cfg) if err != nil { glog.Fatalf("Error building example clientset: %s", err.Error()) } studentInformerFactory := informers.NewSharedInformerFactory(studentClient, time.Second*30) //得到controller controller := NewController(kubeClient, studentClient, studentInformerFactory.Bolingcavalry().V1().Students()) //启动informer go studentInformerFactory.Start(stopCh) //controller开始处理消息 if err = controller.Run(2, stopCh); err != nil { glog.Fatalf("Error running controller: %s", err.Error()) } } func init() { flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.") }
至此,所有代码已经编写完毕,接下来是编译构建;
编译构建和启动
- 在$GOPATH/src/k8s_customize_controller目录下,执行以下命令:
go get k8s.io/client-go/kubernetes/scheme \ && go get github.com/golang/glog \ && go get k8s.io/kube-openapi/pkg/util/proto \ && go get k8s.io/utils/buffer \ && go get k8s.io/utils/integer \ && go get k8s.io/utils/trace
- 上述脚本将编译过程中依赖的库通过go get方式进行获取,属于笨办法,更好的方法是选用一种包依赖工具,具体的可以参照k8s的官方demo,这个代码中同时提供了godep和vendor两种方式来处理上面的包依赖问题,地址是:https://github.com/kubernetes/sample-controller
- 解决了包依赖问题后,在$GOPATH/src/k8s_customize_controller目录下执行命令go build,即可在当前目录生成k8s_customize_controller文件;
- 将文件k8s_customize_controller复制到k8s环境中,记得通过chmod a+x命令给其可执行权限;
- 执行命令./k8s_customize_controller -kubeconfig=$HOME/.kube/config -alsologtostderr=true,会立即启动controller,看到控制台输出如下:
[root@master 31]# ./k8s_customize_controller -kubeconfig=$HOME/.kube/config -alsologtostderr=true I0331 23:27:17.909265 21540 controller.go:72] Setting up event handlers I0331 23:27:17.909450 21540 controller.go:96] 开始controller业务,开始一次缓存数据同步 I0331 23:27:18.110448 21540 controller.go:101] worker启动 I0331 23:27:18.110516 21540 controller.go:106] worker已经启动 I0331 23:27:18.110653 21540 controller.go:181] 这里是student对象的期望状态: &v1.Student{TypeMeta:v1.TypeMeta{Kind:"Student", APIVersion:"bolingcavalry.k8s.io/v1"}, ObjectMeta:v1.ObjectMeta{Name:"object-student", GenerateName:"", Namespace:"default", SelfLink:"/apis/bolingcavalry.k8s.io/v1/namespaces/default/students/object-student", UID:"92927d0d-5360-11e9-9d2a-000c29f1f9c9", ResourceVersion:"310395", Generation:1, CreationTimestamp:v1.Time{Time:time.Time{wall:0x0, ext:63689597785, loc:(*time.Location)(0x1f9c200)}}, DeletionTimestamp:(*v1.Time)(nil), DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string(nil), Annotations:map[string]string{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"bolingcavalry.k8s.io/v1\",\"kind\":\"Student\",\"metadata\":{\"annotations\":{},\"name\":\"object-student\",\"namespace\":\"default\"},\"spec\":{\"name\":\"张三\",\"school\":\"深圳中学\"}}\n"}, OwnerReferences:[]v1.OwnerReference(nil), Initializers:(*v1.Initializers)(nil), Finalizers:[]string(nil), ClusterName:"", ManagedFields:[]v1.ManagedFieldsEntry(nil)}, Spec:v1.StudentSpec{name:"", school:""}} ... I0331 23:27:18.111105 21540 controller.go:182] 实际状态是从业务层面得到的,此处应该去的实际状态,与期望状态做对比,并根据差异做出响应(新增或者删除) I0331 23:27:18.111187 21540 controller.go:145] Successfully synced 'default/object-student' I0331 23:27:18.112263 21540 event.go:209] Event(v1.ObjectReference{Kind:"Student", Namespace:"default", Name:"object-student", UID:"92927d0d-5360-11e9-9d2a-000c29f1f9c9", APIVersion:"bolingcavalry.k8s.io/v1", ResourceVersion:"310395", FieldPath:""}): type: 'Normal' reason: 'Synced' Student synced successfully
至此,自定义controller已经启动成功了,并且从缓存中获取到了上一章中创建的对象的信息,接下来我们在k8s环境对Student对象做增删改,看看controller是否能做出响应;
验证controller
- 新开一个窗口连接到k8s环境,新建一个名为new-student.yaml的文件,内容如下:
apiVersion: bolingcavalry.k8s.io/v1 kind: Student metadata: name: new-student spec: name: "李四" school: "深圳小学"
- 在new-student.yaml所在目录执行命令kubectl apply -f new-student.yaml;
- 返回controller所在的控制台窗口,发现新输出了如下内容,可见新增student对象的事件已经被controller监听并处理:
I0331 23:43:03.789894 21540 controller.go:181] 这里是student对象的期望状态: &v1.Student{TypeMeta:v1.TypeMeta{Kind:"", APIVersion:""}, ObjectMeta:v1.ObjectMeta{Name:"new-student", GenerateName:"", Namespace:"default", SelfLink:"/apis/bolingcavalry.k8s.io/v1/namespaces/default/students/new-student", UID:"abcd77d6-53cb-11e9-9d2a-000c29f1f9c9", ResourceVersion:"370653", Generation:1, CreationTimestamp:v1.Time{Time:time.Time{wall:0x0, ext:63689643783, loc:(*time.Location)(0x1f9c200)}}, DeletionTimestamp:(*v1.Time)(nil), DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string(nil), Annotations:map[string]string{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"bolingcavalry.k8s.io/v1\",\"kind\":\"Student\",\"metadata\":{\"annotations\":{},\"name\":\"new-student\",\"namespace\":\"default\"},\"spec\":{\"name\":\"李四\",\"school\":\"深圳小学\"}}\n"}, OwnerReferences:[]v1.OwnerReference(nil), Initializers:(*v1.Initializers)(nil), Finalizers:[]string(nil), ClusterName:"", ManagedFields:[]v1.ManagedFieldsEntry(nil)}, Spec:v1.StudentSpec{name:"", school:""}} ... I0331 23:43:03.790076 21540 controller.go:182] 实际状态是从业务层面得到的,此处应该去的实际状态,与期望状态做对比,并根据差异做出响应(新增或者删除) I0331 23:43:03.790120 21540 controller.go:145] Successfully synced 'default/new-student' I0331 23:43:03.790141 21540 event.go:209] Event(v1.ObjectReference{Kind:"Student", Namespace:"default", Name:"new-student", UID:"abcd77d6-53cb-11e9-9d2a-000c29f1f9c9", APIVersion:"bolingcavalry.k8s.io/v1", ResourceVersion:"370653", FieldPath:""}): type: 'Normal' reason: 'Synced' Student synced successfully
- 接下来您也可以尝试修改和删除已有的Student对象,观察controller控制台的输出,确定是否已经监听到所有student变化的事件,例如删除的事件日志如下:
I0331 23:44:37.236090 21540 controller.go:171] Student对象被删除,请在这里执行实际的删除业务: default/new-student ... I0331 23:44:37.236118 21540 controller.go:145] Successfully synced 'default/new-student'
小结
至此,controller的编码和验证就全部完成了,现在小结一下自定义controller开发的整个过程:
- 创建CRD(Custom Resource Definition),令k8s明白我们自定义的API对象;
- 编写代码,将CRD的情况写入对应的代码中,然后通过自动代码生成工具,将controller之外的informer,client等内容较为固定的代码通过工具生成;
- 编写controller,在里面判断实际情况是否达到了API对象的声明情况,如果未达到,就要进行实际业务处理,而这也是controller的通用做法;
- 实际编码过程并不负载,动手编写的文件如下:
├── controller.go ├── main.go └── pkg ├── apis │ └── bolingcavalry │ ├── register.go │ └── v1 │ ├── doc.go │ ├── register.go │ └── types.go └── signals ├── signal.go ├── signal_posix.go └── signal_windows.go
以上就是k8s自定义controller的整个开发过程,希望在您的开发过程中本文能提供一些参考;
欢迎关注我的公众号:程序员欣宸
-
k8s|ingres-nginx-controller 安装 | 最新版
2022-03-19 20:50:11文章目录前言1、 Ingress和Pod的关系2、 ingress工作流程3、 安装 ingress controller4、测试1、创建nginx pod5、问题1、 nginx ingress contrller EXTERNAL-IP pending2、IngressClass问题原因:解决一:解决二: ...文章目录
ingress
官方网站
ingress
仓库地址前言
原来我们需要将端口号对外暴露,通过ip+端口号就可以访问
原来是使用service中的NodePort实现的
- 每个节点都会启动端口,
- 在访问的时候通过任何节点,通过ip+端口号就能实现访问
但是NodePort还存在一些缺陷
- 因为端口不能重复,所以每个端口只能使用一次,一个端口对应一个应用
- 实际访问中都是使用域名,根据不同的域名跳转到不同的端口中的
1、 Ingress和Pod的关系
Pod和Ingress都是通过service进行关联的,而作为统一入口,由service关联一组pod中
- 首先service关联我们的pod
- 然后ingress作为入口,首先需要到service,然后发现一组pod
- 发现pod后,就可以做负载均衡等操作
2、 ingress工作流程
实际访问中,我们都是有域名的,维护很多域名,a.com和b.com
然后不同的域名对应不同的service,然后service 管理不同的pod
需要注意ingress不是内置的组件,需要我们单独安装。
3、 安装 ingress controller
下面我们通过yaml的方式,部署我们的ingress,配置如下
# 下载对应的yml wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.1/deploy/static/provider/cloud/deploy.yaml # 替换镜像地址(国内无法下载) sed -i 's@k8s.gcr.io/ingress-nginx/controller:v1.1.1\(.*\)@duangx/ingress-nginx-controller:v1.1.@' deploy.yaml sed -i 's@k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1\(.*\)$@duangx/kube-webhook-certgen:v1.1.1@' deploy.yaml # 修改yml文件名 mv deploy.yaml ingress-nginx.yaml # 部署 kubectl apply -f ingress-nginx.yaml # 卸载 kubectl delete -f ingress-nginx.yaml
4、测试
1、创建nginx pod
# 创建命名空间 kubectl create ns test # 在test命名空间创建nginx kubectl create deployment test-ingress --image=nginx -n test # 暴漏端口 kubectl expose deployment test-ingress --port=80 --target-port=80 --type=NodePort -n test
创建ingress服务
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-service namespace: test spec: rules: - host: www.ixx.com # 自定义的域名 http: paths: - pathType: Prefix path: / backend: service: name: test-ingress # 上面创建的服务名 svc port: number: 80
5、问题
1、 nginx ingress contrller EXTERNAL-IP pending
访问 www.ixx.com 发现访问不通
查看nginx ingress contrller svc 发现 ingress-nginx-controller 的 EXTERNAL-IP 一直pending
[root@k8smaster ingress]# kubectl get svc -n ingress-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller LoadBalancer 10.96.161.205 <pending> 80:30657/TCP,443:30521/TCP 138m ingress-nginx-controller-admission ClusterIP 10.96.178.244 <none> 443/TCP 138m # 查看 ingress-nginx-ctroller 节点 [root@k8smaster ingress]# kubectl get po -n ingress-nginx -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ingress-nginx-admission-create--1-rp5dt 0/1 Completed 0 153m 10.244.243.1 mcn-test <none> <none> ingress-nginx-admission-patch--1-w2vrg 0/1 Completed 3 (153m ago) 153m 10.244.206.65 mcn-prod <none> <none> ingress-nginx-controller-69d84f9c5f-vv9sb 1/1 Running 0 147m 10.244.206.67 mcn-prod <none> <none> # 修改 EXTERNAL-IP为 ingress-nginx-controller(mcn-prod) 的ip(内网)) kubectl edit -n ingress-nginx service/ingress-nginx-controller # 添加 externalIPs:
访问 http://www.ixx.com/ (这个其实不是我们启动的nginx)
2、IngressClass
进入容器修改默认页面
# 进入容器 kubectl exec -it test-ingress-5c4bb6ff8-kl9ql /bin/bash -n test [root@k8smaster ~]# kubectl exec -it test-ingress-5c4bb6ff8-kl9ql /bin/bash -n test kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. root@test-ingress-5c4bb6ff8-kl9ql:/# echo "test-nginx" > /usr/share/nginx/html/index.html
再次访问
查看 ingress-nginx-controller 日志
[root@k8smaster ~]# kubectl log -f ingress-nginx-controller-69d84f9c5f-vv9sb -n ingress-nginx Error: unknown command "log" for "kubectl" Did you mean this? top logs Run 'kubectl --help' for usage. [root@k8smaster ~]# kubectl logs -f ingress-nginx-controller-69d84f9c5f-vv9sb -n ingress-nginx ------------------------------------------------------------------------------- NGINX Ingress controller Release: v1.1.1 Build: a17181e43ec85534a6fea968d95d019c5a4bc8cf Repository: https://github.com/kubernetes/ingress-nginx nginx version: nginx/1.19.9 ------------------------------------------------------------------------------- W0319 05:20:50.050104 7 client_config.go:615] Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work. I0319 05:20:50.050214 7 main.go:223] "Creating API client" host="https://10.96.0.1:443" I0319 05:20:50.064789 7 main.go:267] "Running in Kubernetes cluster" major="1" minor="22" git="v1.22.3" state="clean" commit="c92036820499fedefec0f847e2054d824aea6cd1" platform="linux/amd64" I0319 05:20:50.226998 7 main.go:104] "SSL fake certificate created" file="/etc/ingress-controller/ssl/default-fake-certificate.pem" I0319 05:20:50.242856 7 ssl.go:531] "loading tls certificate" path="/usr/local/certificates/cert" key="/usr/local/certificates/key" I0319 05:20:50.270217 7 nginx.go:255] "Starting NGINX Ingress controller" I0319 05:20:50.277940 7 event.go:282] Event(v1.ObjectReference{Kind:"ConfigMap", Namespace:"ingress-nginx", Name:"ingress-nginx-controller", UID:"ca616501-792c-4f4b-95a3-453cd05cab36", APIVersion:"v1", ResourceVersion:"8881", FieldPath:""}): type: 'Normal' reason: 'CREATE' ConfigMap ingress-nginx/ingress-nginx-controller I0319 05:20:51.472200 7 nginx.go:297] "Starting NGINX process" I0319 05:20:51.472265 7 leaderelection.go:248] attempting to acquire leader lease ingress-nginx/ingress-controller-leader... I0319 05:20:51.472626 7 nginx.go:317] "Starting validation webhook" address=":8443" certPath="/usr/local/certificates/cert" keyPath="/usr/local/certificates/key" I0319 05:20:51.472776 7 controller.go:155] "Configuration changes detected, backend reload required" I0319 05:20:51.480203 7 leaderelection.go:258] successfully acquired lease ingress-nginx/ingress-controller-leader I0319 05:20:51.480368 7 status.go:84] "New leader elected" identity="ingress-nginx-controller-69d84f9c5f-vv9sb" I0319 05:20:51.519115 7 controller.go:172] "Backend successfully reloaded" I0319 05:20:51.519175 7 controller.go:183] "Initial sync, sleeping for 1 second" I0319 05:20:51.519257 7 event.go:282] Event(v1.ObjectReference{Kind:"Pod", Namespace:"ingress-nginx", Name:"ingress-nginx-controller-69d84f9c5f-vv9sb", UID:"84dfa8f2-de4b-4191-872b-8599510919f3", APIVersion:"v1", ResourceVersion:"9612", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration I0319 06:07:33.859343 7 admission.go:149] processed ingress via admission controller {testedIngressLength:1 testedIngressTime:0.024s renderingIngressLength:1 renderingIngressTime:0s admissionTime:18.0kBs testedConfigurationSize:0.024} I0319 06:07:33.859366 7 main.go:101] "successfully validated configuration, accepting" ingress="test/ingress-service" I0319 06:07:33.863734 7 store.go:420] "Ignoring ingress because of error while validating ingress class" ingress="test/ingress-service" error="ingress does not contain a valid IngressClass" # 错误 ingress does not contain a valid IngressClass
问题原因:
ingress does not contain a valid IngressClass
除了可能会有多个不同类型的 Ingress Controller 之外,还可能存在多个相同类型的 Ingress Controller,比如部署了两个 NGINX Ingress Controller,一个负责处理外网访问,一个负责处理内网访问。
此时也可以通过上面的方式,为每个 Controller 设定唯一的一个 class。
当多个 controller 的 class 不唯一,或者 controller 和 Ingress 都没有指定 class 又没有默认的 class 时,会导致所有符合条件的 Ingress Controller 竞争满足 Ingress 配置,可能会导致不可预测的结果。
解决一:
创建ingress 添加 IngressClass属性
查看 IngressClass
[root@k8smaster ingress]# kubectl get ingressClass NAME CONTROLLER PARAMETERS AGE nginx k8s.io/ingress-nginx <none> 3h50m
修改我们的service
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-service namespace: test spec: # 这一步需要指定ingressClassName,不然信息无法同步到nginx配置中,也会报 ingress does not contain a valid Ingress Class 错误 ngressClassName: nginx rules: - host: www.ixx.com # 自定义的域名 http: paths: - pathType: Prefix path: / backend: service: name: test-ingress # 上面创建的服务名 svc port: number: 80
重新应用
[root@k8smaster ingress]# vim test-ingress.yml [root@k8smaster ingress]# kubectl apply -f test-ingress.yml ingress.networking.k8s.io/ingress-service configured
解决二:
设置默认的 IngressClass 注意:修改后必须新创建的ingress才会默认使用
在集群中,我们可以设定一个默认的 Ingress Class,以便处理所有没有指定 Ingress Class 的 Ingress 资源。
在
IngressClass
资源上,我们可以通过将ingressclass.kubernetes.io/is-default-class
注解的值设定为true
,来使没有设置ingressClassName
的 Ingress 使用此默认的IngressClass
。注意:当存在多个默认 Ingress Class 时,新的 Ingress 如果没有指定
ingressClassName
则不会被允许创建。解决这个问题只需确保集群中最多只能有一个 IngressClass 被标记为默认。 -
controller层中注入controller
2020-12-08 15:25:44问题描述:在controller中注入了另一个controller,按理说,三个注解controller、service、respository都是component衍生而来,任何被注入容器中的bean都可以以同样方式获取,但是在使用springboot项目时,... -
Realtek PCIe GBE Family Controller WIN7/WIN2008 驱动
2015-01-21 17:10:21Realtek PCIe GBE Family Controller WIN7/WIN2008 驱动 支持32/64位,WIN2008数据中心版亲测通过 -
springMVC基本配置(Controller+Service+Dao)
2014-12-31 11:51:53搭建SpringMVC模型,包含控制层(@Controller)、 业务逻辑层Service(@Service)、持久层(@Repository)三层注入方式。 -
Kafka Controller工作原理
2020-12-05 23:52:18Controller脑裂 为了解决Controller脑裂问题,ZooKeeper中还有一个与Controller有关的持久节点/controller_epoch,存放的是一个整形值,用于记录Controller发生变更的次数,即记录当前是第几代Controller,也称为... -
BaseController
2020-05-29 11:17:55在ssm项目中,多个controller会有很多相同的代码或使用相同的功能,例如增删查改,这时我们就可以编写一个BaseController了,然后就继承该类,需要时再使用里面的方法。BaseController代码如下: package ... -
『Java安全』Java Spring内存马_动态注册Controller内存马
2022-04-03 20:20:08文章目录前言Controller... 写恶意ControllerⅣ. 反射获取恶意方法Ⅴ. 向RequestMappingHandlerMapping注册代码运行截图注意事项参考引用完 前言 SpringBoot版本2.4.5 <parent> <groupId>org.springframe -
ASP.NET MVC中ApiController与Controller的区别
2020-06-22 13:23:52本文翻译自:Difference between ApiController and Controller in ASP.NET MVC I've been playing around with ASP.NET MVC 4 beta and I see two types of controllers now: ApiController and Controller .... -
springboot的controller中接口调接口(controller调用controller、controller内部接口互相调用)
2021-11-29 12:44:18场景: 公司封装了一个日志产品,是以切片(AOP)的形式进行日志记录的,也就是在接口返回值中...其实就是在Controller中注入本Controller自身,然后通过注入的对象调用同一个Controller中的其他接口,有调用别的Contro -
Pocket Controller-Pro6.0汉化破解版
2013-06-20 11:36:16可以通过PC远程控制WINCE设备的软件,实现类似于QQ的远程协助功能。 同时支持 ActiveSync 和 Wifi 两种方式的连接。 压缩包里的 myPCP.exe 是破解补丁。...不用安装,直接运行 PocketController.exe 即可。 -
Spring Boot(3)之 Controller 参数传递
2022-01-17 10:07:58Controller的参数传递 获取URL中的参数 1、Thymeleaf模板 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text=... -
Controller层编码规范
2019-05-28 11:04:141、Controller层 controller层在MVC设计中属于控制层;设计初衷:接受请求并响应请求;所以,该层尽量轻薄,避免编写涉及业务处理的代码。 前后端分离的开发设计模式下,推荐使用@RestController注解,它相当于@... -
ApplePS2Controller
2011-11-08 13:59:48ApplePS2Controller.kext说明: 苹果系统驱动文件 -
Unity3D 角色控制器CharacterController
2013-08-21 22:09:24文章Unity3D学习笔记04:角色控制器CharacterController控制人物移动旋转 的项目源码,免费下载。 -
详解Spring Boot中Controller用法
2021-03-06 20:12:48ControllerController是SpringBoot里最基本的组件,他的作用是把用户提交来的请求通过对URL的匹配,分配个不同的接收器,再进行处理,然后向用户返回结果。他的重点就在于如何从HTTP请求中获得信息,提取参数,并... -
Character controller
2021-12-01 10:51:08该blog是本人在金匠学习独立游戏期间上课笔记,欢迎纠正,补充 ...https://assetstore.unity.com/packages/essentials/starter-assets-first-person-character-controller... -
Controller(Kubernetes)的ControllerExpectations解析
2021-03-28 13:45:28本文涉及的部分名词参看名词解释,此处还要强调两个概念:Controller和Controllee,在本文其实二者是同一个意思,代表XxxController(比如DeploymentController)管理的Xxx(比如Deployment)对象。... -
Unity中的Character Controller 简介
2022-04-07 10:48:37unity使用Character Controller进行角色控制操作 -
Controller(一)
2021-06-22 17:56:54一、Controller是什么? 顾名思义,Controller就是控制器。它是玩家在游戏中的一个代理对象,也是玩家与游戏中人物或者AI之间的交互的桥梁。它的任务就是掌管人物或AI的所有行为。可以说Controller将人物和AI玩弄...