(二)Spring自动装配
Spring自动装配
为了减少XML的配置数量。Spring提供了几种技巧来解决这一问题:
自动装配(autowiring): 有助于减少<property>
元素和<constroctor-arg>
元素,让Spring自动识别如何装配Bean的依赖关系
自动检测(autodiscovery): 让Spring自动识别那些类需要被配置成Spring Bean
,从而减少对<bean>
元素的使用
<!--more-->
自动装配Bean属性
4种类型的自动装配
byName: 把与Bean的属性具有相同名字(或ID)的其他Bean自动装配到Bean的对应属性中。如果没有跟属性的名字相匹配的Bean,该属性不进行装配。
byType: 把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。若没有相匹配的Bean,则不装配。
constructor: 把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean的构造器的对应入参中。
autodetect: 首先尝试用
constructor
进行自动装配,若失败就再次尝试用byType
进行自动装配。
byName自动装配
通过byName
元素Spring可以实现自动查找其他Bean中是否有相同的属性名称,有则进行装配。体会下面的案例:
EmpService.java
package demo2; public class EmpService { //注入Dao private EmpDao empDao; public void setEmpDao(EmpDao empDao) { System.out.println("EmpService --> EmpDao"); this.empDao = empDao; } }
EmpDao.java
package demo2; public class EmpDao {}
spring.xml
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="empService" class="demo2.EmpService" autowire="byName"/> <bean id="empDao" class="demo2.EmpDao"/> </beans>
Test测试类
@Test public void run(){ //加载Spring上下文 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); ac.getBean("empService"); }
打印结果:
情况一:如果通过之前的方式通过ref
来注入一个引用Bean呢?我们改变上述spring.xml
中的代码:
<bean id="empDao" class="demo2.EmpDao"/> <bean id="empService" class="demo2.empService"> <property name="empDao" ref="empDao"/> </bean>
结果和上述相同。
情况二:如果我们改变EmpService.java
中empDao
的数据类型
package demo2; public class EmpService { //注入Dao private String empDao; public void setEmpDao(String empDao) { System.out.println("EmpService --> EmpDao"); this.empDao = empDao; } }
打印结果:
观察发现autowire="byName"
自动装配的含义就是:
若BeanA
中存在与BeanB
的idid="empDao"
相同的属性(且存在setter方法)private EmpDao empDao
;Spring就能自动进行装配。
那么,开始我一直疑惑这个byName
注入不是查找的bean名称和另一个bean的属性名称相同就能注入吗,其实并不是。首先我们要明白注入是注入另一个类或接口什么的,Spring让这两个bean之间产生了联系,即我们定义一个普通类型的参数private String empDao
,即使名称和另一个Bean相同empDao
,但是它只是一个属性,怎么能让一个普通的参数注入到另一个Bean中呢?(注意这里我是在自动注入的前提下说的,当然是可以实现注入的)。
缺点: byName
的缺点就是首先要存在这个和另一个Bean的id相同的属性,其次,如果多个Bean中都存在这个属性,那Spring都会将这些Bean装配进去。如下案例:
新创建一个UserDao.java
package demo2; public class EmpDao {}
EmpService.java
package demo2; public class EmpService { //注入Dao private EmpDao empDao; public void setEmpDao(EmpDao empDao) { System.out.println("EmpService --> EmpDao"); this.empDao = empDao; } private UserDao userDao; public void setUserDao(UserDao userDao) { System.out.println("EmpService --> UserDao"); this.userDao = userDao; } }
在spring.xml
中注入该Bean:
<bean id="userDao" class="demo2.UserDao"/>
观察结果:
如上就体现了这中注入方法的缺陷,就是如果多个Bean的id
名称都和另一个bean的属性有相同的,那么Spring会全部注入。
byType自动装配
byType
自动装配的方法和byName
装配方式类似,区别在于byType
找的是相同的数据类型,而byName
找的是相同的名称
。
EmpService.java
package demo2; public class EmpService { //注入Dao private EmpDao emp; public void setEmpDao(EmpDao emp) { System.out.println("EmpService --> EmpDao"); this.emp = emp; } }
spring.xml
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="empService" class="demo2.EmpService" autowire="byType"/> <bean id="empDao" class="demo2.EmpDao"/> </beans>
Test测试类
@Test public void run(){ //加载Spring上下文 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); ac.getBean("empService"); }
打印结果:
观察:上面EmpDao
bean的id是empDao
,但EmpService
中存在的属性是private EmpDao emp;
,注意不是empDao
,那么当我们配置了autowire="byType"
后,因为数据类型相同,Spring仍可将EmpDao
注入到EmpService
中。
缺点
如果Spring找到了多个Bean的,他们的类型与需要自动装配的属性的类型都匹配,那么Spring会抛出异常,而不是选择注入哪个。如下情况:
其他不变,我们在spring.xml
增加一行配置:
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="empService" class="demo2.EmpService" autowire="byType"/> <bean id="empDao" class="demo2.EmpDao"/> <bean id="dao" class="demo2.EmpDao"/> </beans>
打印结果:
可以看到,当我们在spring.xml
中注入两个类型相同的Bean(当然名称不能相同),此时就会报错。
为解决这一错误,Spring提供了pimary
和autowire-candidate
两个属性。
为自动装配标识一个首选Bean,使用
primary
属性,如果设置primary="true"
那么该Bean会被优先选择。但Spring却给每一个<bean>
默认都配置了primary="true"
属性,也就是此时每个Bean都是首选的,而你想指定哪个Bean是首选就必须设置其他Bean都是primary="false"
候选。所以说:primary
属性仅对标识的首选Bean有意义。如果想解决上面多个Bean都符合情况造成的报错,必须引入下面属性:即为想被忽略注入的
<bean>
配置autowire-candidate="false"
属性,这样Spring在进行装配时就会忽略autowire-candidate="false"
标识的Bean。
spring.xml
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="empService" class="demo2.EmpService" autowire="byType"/> <bean id="empDao" class="demo2.EmpDao"/> <bean id="dao" class="demo2.EmpDao" autowire-candidate="false"/> </beans>
constructor自动装配
如果要通过构造器来装配Bean,那我们可以移除<constructor-arg>
元素,采用Spring自动注入的方式:(注意这里我们改变了以上的代码逻辑,增加了一个Emp
接口,让EmpDao
和UserDao
都继承这个接口)
Emp.java
package demo2; public interface Emp {}
EmpDao.java
package demo2; public class EmpDao implements Emp {}
UserDao.java
package demo2; public class UserDao implements Emp {}
EmpService.java
package demo2; public class EmpService { private Emp emp; public void setEmp(Emp emp) { System.out.println("执行..."); this.emp = emp; } public EmpService(Emp emp) { System.out.println("这是构造参数..."); this.emp = emp; } }
同时这种方式也存在和byType
一样的缺点:
spring.xml
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="empService" class="demo2.EmpService" autowire="constructor"/> <bean id="empDao" class="demo2.EmpDao"/> <bean id="userDao" class="demo2.UserDao"/> </beans>
结果仍报错:
最佳自动装配—>由Spring决定怎么装配
<bean id="empService" class="demo2.EmpService" autowire="autodetect"/>
默认自动装配
<beans .... default-autowire="xxx" >
使用注解自动装配
从Spring2.5开始可以使用注解实现自动装配,注解装配和使用XML中的autowire
本身没有太大区别,但是注解装配可以实现更小颗粒度的装配。但是Spring默认是禁用注解装配的,所以我们首先要在spring.xml
中启用注解装配。最简单的方式就是启用Spring的context
命名空间配置中的<context:annotation-config>
元素,如下:
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> </beans>
Spring3支持几种不同的自动装配注解:
Spring自带的@Autowired注解
JSR-330的@Inject注解
JSR-250的@Resource注解
使用@Autowired
如下案例:
spring.xml
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean id="empService" class="demo2.EmpService"/> <bean id="empDao" class="demo2.EmpDao"/> </beans>
EmpService.java
package demo2; import org.springframework.beans.factory.annotation.Autowired; public class EmpService { private Emp emp; @Autowired public void setEmp(Emp emp) { System.out.println("执行..."); this.emp = emp; } public void play(){ emp.play(); } }
Emp.java
package demo2; public interface Emp { void play(); }
EmpDao.java
package demo2; public class EmpDao implements Emp { public void play() { System.out.println("EmpDao: play..."); } }
Test测试类
@Test public void run(){ //加载Spring上下文 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); EmpService es = (EmpService) ac.getBean("empService"); es.play(); }
除了上面标记在setter方法上,还可以直接标记到私有属性上:
package demo2; import org.springframework.beans.factory.annotation.Autowired; public class EmpService { @Autowired private Emp emp; public void setEmp(Emp emp) { System.out.println("执行..."); this.emp = emp; } public void play(){ emp.play(); } }
可以看到,使用这种方式仍可以完成注入,并且我们发现将@Autowired
直接标记到私有属性上,并不会执行setter方法,但是仍完成了注入,这其实就是@Autowired
的一大优势,它简化了setter方法的书写,采用Java的反射机制完成的注入。
缺点:采用@Autowired
注解注入固然方便,但是也存在缺点,即被标注的属性或参数必须是可注入的,如下列情况,我们将emp
的数据类型改变(注意这里要注释掉play
方法,因为数据类型改变了也就不能再调用play方法):
package demo2; import org.springframework.beans.factory.annotation.Autowired; public class EmpService { @Autowired // private Emp emp; private String emp; /*public void play(){ emp.play(); }*/ }
此时就会报错NoSuchBeanDefinitionException
,因为我们认为属性不一定要被装配,返回null值也好,从而避免抛出异常,那么我们就可以在@Autowired
中添加required
属性,表示是否一定要进行装配:
package demo2; import org.springframework.beans.factory.annotation.Autowired; public class EmpService { @Autowired(required = false) // private Emp emp; private String emp; /*public void play(){ emp.play(); }*/ }
这样就不会再出现保报错的情况。
注意: required
属性可以用于@Autowired
注解所使用的任意地方,但当使用构造器进行装配时,只有一个构造器可以将@Autowired
的required
属性设置为true
,其他都必须设置为required=false
。此外,使用@Autowired
标记多个构造器时,Spring会从所有瞒住装配条件的构造器中选择入参最多的构造器。
限定歧义性的依赖
对于出现多个Bean满足注入条件的解决,上面也介绍了一系列的方法,即使是上面的@Autowired
细颗粒度的注入方式仍然无法解决多个Bean满足注入条件的情况,所以Spring提供了一个更好的方式解决这一问题:
使用@Qualifier("beanName")
限定注入哪个Bean
spring.xml
<bean id="empService" class="demo2.EmpService"/> <bean id="empDao" class="demo2.EmpDao"/> <bean id="userDao" class="demo2.UserDao"/>
此时,因为EmpDao
和UserDao
都实现了Emp
接口,所以empDao
和userDao
两个Bean都瞒住注入条件,此时就会产生NoSuchBeanDefinitionException
的错误。
EmpService.java
package demo2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; public class EmpService { @Autowired(required = false) @Qualifier("empDao") private Emp emp; public void play(){ emp.play(); } }
即这里我们限定了将empDao
注入,而不是注入userDao
,这样就会避免出现错误。
@Inject基于标准的自动装配
为了统一各种依赖注入框架编程模型,JCP(Java Community Process)发布了一套Java的依赖注入规范,@Inject注解则是其核心部件,该注解和@Autowired几乎相同,但对于一些特殊情况,@Inject也有自己的处理办法:
首先@Inject没有required属性,所以被标记的属性必须是可注入的
歧义性的Bean定义:@Qualifier —> @Named
在注解注入中使用表达式
@Value
自动检测Bean
当在Spring配置中增加了<context:annotation-config>
时,我们希望Spring特殊对待我们所定义的Bean里的某一组注解,并使用这些注解指导Bean的装配,由此产生了 <context:component-scan
元素。它能大大简化对<bean>
的配置,通过在<context:component-scan>
元素中指定要扫描指定包下的Bean对象,如:
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="demo2"/> </beans>
那么<context:component-scan>
又是如何知道哪些类需要注册为Spring Bean呢?
为自动检测标注Bean
<context:component-scan>
会查找使用构造型(stereotype)注解所标注的类
@Component —>通用的构造性注解,标识该类为Spring组件。
@Controller —>标识将该类定义为Spring MVC controller
@Repository —>标识将该类定义为数据仓库
@Service —>标识将该类定义为服务
使用@Component标注的任意自定义注解
举例:
spring.xml
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="demo2"/> </beans>
如上我们只需要开启注解支持.
EmpService.java
package demo2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class EmpService { @Autowired(required = false) private Emp emp; @Bean public EmpDao empDao(){ return new EmpDao(userDao()); } public void play(){ emp.play(); } }
如上,我们首先使用@Configuration
标记的Java类,就等价于XML配置中的<beans>
元素。使用@Bean
就等价于<bean>
元素,(@Bean
告知Spring这个方法将返回一个对象,该对象应该被注册为Spring应用上下文中的一个Bean。方法名将作为该Bean的ID。在该方法中所实现的所有逻辑本质上都是为了创建Bean)这样我们就实现了将一个JavaBean对象交付给Spring管理,再添加@Autowired
就完成了自动注入的功能。这是注入一个简单对象,那么我们怎么注入一个引用呢?
package demo2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class EmpService { @Qualifier("empDao") @Autowired(required = false) private Emp emp; @Bean public UserDao userDao(){ return new UserDao(); } @Bean public EmpDao empDao(){ return new EmpDao(userDao()); } }
向另一个Bean中注入一个引用,只需要在此Bean的返回值中调用另外一个被@Bean
标记的方法名即可完成注入,但是仍要考虑歧义性问题(使用@Qualifier
进行限定)
总结
综上:我们已经介绍了Spring自动装配和注解开发的相关知识,下面我们来回顾一下。
自动装配
byName: 把与Bean的属性(在JavaBean中定义的属性)和具有相同名字(ID)的其他Bean自动装配到Bean的对应属性中。
byType: 把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。
constructor: 把与Bean的构造器参数入参具有相同类型的其他Bean自动装配到Bean的对应入参中。
autodetect: 首先舱室使用
constructor
进行自动装配,如果装配失败再尝试使用byType
进行装配。
注解开发
自动装配提供的注解:
@Autowired — required=false — @Qualifier("beanName")
@Inject — @Named
Spring基于Java的注解开发:
@Configuration(
<beans>
) @Bean(
<bean>
)
注意问题:
以上的自动装配的方式都无法避免多个Bean同时满足注入条件的情况,但是Spring针对不同的注入方式提供了不同的解决办法:
byName
会对满足条件的Bean都进行封装,不加考虑。byType
对遇到多个满足注入条件的Bean时会抛出异常,同时给出primary
(仅对首选Bean有意义)、autowire-candidate="false"
(忽略某个Bean的自动装配)。(注:我们要明白Spring默认将所有Bean都设置为primary="true"
这样就没有什么首选Bean可言。所以此时我们最好采用autowire-candidate
忽略某个Bean的自动装配。constructor
自动装配和byType
有一样的局限性,会报错。综合以上,提出了最佳的自动装配方式:
autowire="autodetect"
。Spring首先会尝试使用constructor自动装配,如果没有与构造器相匹配的Bean,Spring将尝试使用byType
自动装配。注解开发有常用的三个自动装配方式:1.
@Autowired
(Spring自带的注解); 2.Inject
(基于标准的自动装配);3.@Resource
@Autowired
(默认按照类型装配)避免
@Autowired
标注的属性或参数是不可装配的,提供required=false
属性。限定歧义性的依赖(多个Bean的情况),提供
@Qualifier("beanName")
缩小选择范围,指定某一个Bean
@Inject
注解自动装配和@Autowired
注解相似,但是对上述解决方案给出的注解不同,如以下:@Inject
限定所标记的属性必须是可装配的,不然就会报错@Qualifier
—>@Named
@Resource
(默认按照名称装配)是jdk1.6支持的注解
<br/>
交流
如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!
<br/>
联系
If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.
Blog@TyCoding's blog
GitHub@TyCoding
ZhiHu@TyCoding
(二)Spring自动装配相关推荐
- 一步一步手绘Spring DI运行时序图(Spring 自动装配之依赖注入)
相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...
- SpringBoot源码分析(二)之自动装配demo
SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...
- Spring 自动装配详细讲解
一.什么是自动装配 1.自动装配是使用spring满足bean依赖的一种方法 2.spring会在应用上下文中为某个bean寻找其依赖的bean. 二.自动装配的方法有哪些 1.在xml中显式配置: ...
- Spring 自动装配 ‘byType’
转载自 Spring 自动装配 'byType' Spring 自动装配 'byType' 这种模式由属性类型指定自动装配.Spring 容器看作 beans,在 XML 配置文件中 beans ...
- Spring 自动装配 ‘byName’
转载自 Spring 自动装配 'byName' Spring 自动装配 'byName' 这种模式由属性名称指定自动装配.Spring 容器看作 beans,在 XML 配置文件中 beans 的 ...
- spring自动装配依赖包_解决Spring自动装配中的循环依赖
spring自动装配依赖包 我认为这篇文章是在企业应用程序开发中使用Spring的最佳实践. 使用Spring编写企业Web应用程序时,服务层中的服务量可能会增加. 服务层中的每个服务可能会消耗其他服 ...
- Java的注解机制——Spring自动装配的实现原理
JDK1.5加入了对注解机制的支持,实际上我学习Java的时候就已经使用JDK1.6了,而且除了@Override和@SuppressWarnings(后者还是IDE给生成的--)之外没接触过其他的. ...
- Spring自动装配----注解装配----Spring自带的@Autowired注解
Spring自动装配----注解装配----Spring自带的@Autowired注解 父类 package cn.ychx;public interface Person {public void ...
- spring自动装配、注解
spring自动装配 Spring 自动装配 byName 这种模式由属性名称指定自动装配.Spring 容器看作 beans,在 XML 配置文件中 beans 的 auto-wire 属性设置为 ...
最新文章
- 型人格 disc测试_3号,成就型人格的专业和职业选择@九型人格测试
- 常见机器学习算法背后的数学
- 《按键消抖与LED控制》实验的个人思考与总结
- android 获取短信验证码倒计时
- flume1.8 开发指南学习感悟
- nodejs启动机制分析
- 微软大数据_我对Microsoft的数据科学采访
- 一步步编写操作系统 46 用c语言编写内核3
- html区块位置怎么设置,HTML 区块
- 使用MEF方便的扩展
- Memcached 一致性哈希算法PHP实现
- 面对互联网上的汩汩恶意,如何构建反欺诈体系?
- python程序的扩展名是perl程序的扩展名是_Python 程序扩展名(py, pyc, pyw, pyo, pyd)及发布程序时的选择...
- html图片浮空但占位置,求助:鼠标经过图片时,图片悬浮出现变大
- 深入理解viewport及相关属性的关系
- android模拟器定位失败,Android-无法在模拟器上获取GPS位置
- C语言将字符串转换为数字
- 什么是360度全景图,360度全景图有什么用
- 身为IT人你应该知道的几个威客网站【转】
- 法大大登榜《胡润中国瞪羚企业》
热门文章
- Ubuntu 16.04 安装pyk4a
- 做完系统回来计算机连接不上网络,电脑重装系统后网络连接不上怎么处理
- 4K超高清电视全面支持HDMI技术
- JavaBean技术的使用
- 事业单位面试题 一(自我练习及答案)
- Field tagService in xxx.service.impl.ArticleServiceImpl required a bean of type ‘xxserviceTagService
- 复合类型(compound type)
- 数字图像处理 -灰度变换 之 对数变换(Log Transformation)
- oracle时间戳表达式,Oracle Timestamp类型
- mqtt broker(代理/服务器)mosquitto的安装 配置 使用