在VS Code中调试PHP

php安装xdebug

  • phpinfo()页面html源代码粘贴到Xdebug Installation Wizard
  • xdebug会根据你本地的php环境提供相应的版本下载及配置说明。
  • 安装配置成功后,可以通过xdebug_info()查看。

VS Code配置

  • 安装PHP Debug插件
  • Run And Debug中Open ’launch.json'
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9003
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 0,
            "runtimeArgs": [
                "-dxdebug.start_with_request=yes"
            ],
            "env": {
                "XDEBUG_MODE": "debug,develop",
                "XDEBUG_CONFIG": "client_port=${port}"
            }
        },
        {
            "name": "Launch Built-in web server",
            "type": "php",
            "request": "launch",
            "runtimeArgs": [
                "-dxdebug.mode=debug",
                "-dxdebug.start_with_request=yes",
                "-S",
                "localhost:0"
            ],
            "program": "",
            "cwd": "${workspaceRoot}",
            "port": 9003,
            "serverReadyAction": {
                "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started",
                "uriFormat": "http://localhost:%s",
                "action": "openExternally"
            }
        }
    ]
}
  • 通过Start Debugging启动即可。

小程序组件嵌套防止事件冒泡

在实际使用中,遇到下面这种情况。在父View的绑定事件openImgDialog实现图片预览,子View绑定事件delImage实现删除图片。如下图效果:

<view class="bg-white padding">
  <view class="grid col-4 grid-square">
    <view class="bg-img" wx:for="{{detail.imgInfo}}" wx:key style="background-image:url({{item.url}});" bindtap="openImgDialog" data-url="{{item.url}}">
      <view class="is-tag bg-red" bindtap="delImage" data-id="{{item.id}}">
        <text class="isIcon-close"></text>
      </view>
    </view>
  </view>
</view>

在点击子View删除图片事件时,会同时触发父View预览事件。如下图所示:

事件分为冒泡事件和非冒泡事件:

  • 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
  • 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。

小程序官方文档-事件

将子事件bindtap改为catchtap即可。

<view class="bg-white padding">
  <view class="grid col-4 grid-square">
    <view class="bg-img" wx:for="{{detail.imgInfo}}" wx:key style="background-image:url({{item.url}});" bindtap="openImgDialog" data-url="{{item.url}}">
      <view class="is-tag bg-red" catchtap="delImage" data-id="{{item.id}}">
        <text class="isIcon-close"></text>
      </view>
    </view>
  </view>
</view>

使用Vue CLI构建Vue项目

全局安装vue-cli

npm install -g @vue/cli
# OR
yarn global add @vue/cli
Usage: vue <command> [options]

Options:
  -V, --version                              output the version number
  -h, --help                                 output usage information

Commands:
  create [options] <app-name>                create a new project powered by vue-cli-service
  add [options] <plugin> [pluginOptions]     install a plugin and invoke its generator in an already created project
  invoke [options] <plugin> [pluginOptions]  invoke the generator of a plugin in an already created project
  inspect [options] [paths...]               inspect the webpack config in a project with vue-cli-service
  serve [options] [entry]                    serve a .js or .vue file in development mode with zero config
  build [options] [entry]                    build a .js or .vue file in production mode with zero config
  ui [options]                               start and open the vue-cli ui
  init [options] <template> <app-name>       generate a project from a remote template (legacy API, requires @vue/cli-init)
  config [options] [value]                   inspect and modify the config
  outdated [options]                         (experimental) check for outdated vue cli service / plugins
  upgrade [options] [plugin-name]            (experimental) upgrade vue cli service / plugins
  migrate [options] [plugin-name]            (experimental) run migrator for an already-installed cli plugin
  info                                       print debugging information about your environment

  Run vue <command> --help for detailed usage of given command.

可以通过vue initvue create创建项目。 Vue CLI的包名称由vue-cli改成了@vue/cli。如果你已经全局安装了旧版本的vue-cli (1.x 或 2.x),你需要先通过npm uninstall vue-cli -gyarn global remove vue-cli卸载它。

  vue create is a Vue CLI 3 only command and you are using Vue CLI 2.9.6.
  You may want to run the following to upgrade to Vue CLI 3:

  npm uninstall -g vue-cli
  npm install -g @vue/cli

Vue CLI 2

vue init

λ vue init -h
Usage: vue-init <template-name> [project-name]

Options:
  -c, --clone  use git clone
  --offline    use cached template
  -h, --help   output usage information
  Examples:

    # create a new project with an official template
    $ vue init webpack my-project

    # create a new project straight from a github template
    $ vue init username/repo my-project
vue init webpack [project-name]

项目结构如下

运行

npm run dev

Vue CLI 3

vue create

λ vue create -h
Usage: create [options] <app-name>

create a new project powered by vue-cli-service

Options:
  -p, --preset <presetName>       Skip prompts and use saved or remote preset
  -d, --default                   Skip prompts and use default preset
  -i, --inlinePreset <json>       Skip prompts and use inline JSON string as preset
  -m, --packageManager <command>  Use specified npm client when installing dependencies
  -r, --registry <url>            Use specified npm registry when installing dependencies (only for npm)
  -g, --git [message]             Force git initialization with initial commit message
  -n, --no-git                    Skip git initialization
  -f, --force                     Overwrite target directory if it exists
  --merge                         Merge target directory if it exists
  -c, --clone                     Use git clone when fetching remote preset
  -x, --proxy                     Use specified proxy when creating project
  -b, --bare                      Scaffold project without beginner instructions
  --skipGetStarted                Skip displaying "Get started" instructions
  -h, --help                      output usage information

项目结构如下:

使用图形化界面

vue ui

运行

yarn serve

小程序子组件列表下拉刷新

最近,研究小程序的过程中,遇到一个问题:小程序的组件Component并没有的Page下拉监听事件:

  • onPullDownRefresh 监听用户下拉动作
  • onReachBottom 页面上拉触底事件的处理函数

比如说,下面这样的结构:

  • index page 引用search-head组件
  • search-head component 引用search-list组件
  • search-list component 显示列表数据

那如何才能刷新组件的列表呢:

  1. search-head提供搜索、翻页方法,传递数据给search-list。
  2. search-list组件只显示数据。
  3. page监听onReachBottom事件,然后调用search-head中的方法。

search-head

搜索头部组件,此组件内引用搜索列表组件。在这个组件内,实现搜索、翻页功能。通过组件将数据传递给子组件。

search-head.json

{
  "component": true,
  "usingComponents": {
    "search-list": "/component/search-list"
  }
}

search-head.wxml

<view>
<button type="primary" bindtap="search">搜索</button>
</view>
<search-list list="{{list}}"></search-list>

search-head.js

Component({
  properties: {

  },
  data: {
    list: Array,
    currentPage: 1
  },
  methods: {
    buildObj: function(page) {
      var arr = [];
      for (var i = 1; i <= 30; i++) {
        var obj = {
          id: i,
          value: '第' + page + '页'
        }
        arr.push(obj);
      }
      return arr;
    },
    search: function() {
      let list = this.buildObj(this.data.currentPage);
      this.setData({
        list: list
      });
    },
    getNextPage: function() {
      let currentPage = this.data.currentPage + 1;
      this.setData({
        currentPage: currentPage,
        list: this.data.list.concat(this.buildObj(currentPage))
      });
    }
  },
  lifetimes: {
    attached: function() {
      this.search();
    }
  }
})

search-list

search-list.js

Component({
  properties: {
    list: Array
  }
})

search-list.wxml

<view wx:for="{{list}}" wx:key="key">
  <view>{{item.value}}-{{item.id}}</view>
</view>

index

Page页,引用搜索头部组件。监听onReachBottom事件,调用组件内的翻页方法。

index.js

Page({
  onReachBottom: function() {
    console.log("onReachBottom");
    this.selectComponent("#page").getNextPage();
  }
})

index.json

{
  "usingComponents": {
    "search": "/component/search-head"}
}

index.wxml

<search id="page"></search>

完整代码

优雅的关闭springboot程序

使用jar形式部署springboot程序后,通过kill进程关闭程序,总是不踏实。好在,官方提供了优雅关闭应用程序的方式。

引入依赖

首先,在POM中引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置application

以2.1.8.RELEASE为例,application.yml配置如下:

management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "*"
      base-path: /actuator

发送POST请求

curl -d "" --header "Content-Type:application/json" http://127.0.0.1:8080/actuator/shutdown
{"message":"Shutting down, bye..."}

Spring Boot Actuator

actuator可以帮助你监控和管理Spring Boot应用,比如健康检查、审计、统计和HTTP追踪等。所有的这些特性可以通过JMX或者HTTP endpoints来获得。
Actuator通过创建Endpoint来暴露HTTP或者JMX来监控和管理应用。

Endpoint

Endpoint 说明
auditevents 显示应用暴露的审计事件 (比如认证进入、订单失败)
info 显示应用的基本信息
health 显示应用的健康状态
metrics 显示应用多样的度量信息
loggers 显示和修改配置的loggers
logfile 返回log file中的内容(如果logging.file或者logging.path被设置)
httptrace 显示HTTP足迹,最近100个HTTP request/repsponse
env 显示当前的环境特性
flyway 显示数据库迁移路径的详细信息
liquidbase 显示Liquibase 数据库迁移的纤细信息
shutdown 让你逐步关闭应用
mappings 显示所有的@RequestMapping路径
scheduledtasks 显示应用中的调度任务
threaddump 执行一个线程dump
heapdump 返回一个GZip压缩的JVM堆dump

默认,除了shutdown所有的endpints都是打开的。

打开关闭Endpoint

你可以通过设置management.endpoint.<id>.enabled to true or false来决定打开还是关闭一个actuator endpoint。启用shutdown如下:

management.endpoint.shutdown.enabled=true

暴露Endpoint

通过HTTP暴露Actuator endpoints
management.endpoints.web.exposure.include=* 
management.endpoints.web.exposure.exclude=health,info

通过JMX暴露Actuator endpoints

management.endpoints.jmx.exposure.include=*
management.endpoints.jmx.exposure.exclude=

打开全部Endpoint后,访问http://127.0.0.1:8080/actuator如下:

{
  _links: {
    self: {
      href: "http://127.0.0.1:8080/actuator",
      templated: false
    },
    auditevents: {
      href: "http://127.0.0.1:8080/actuator/auditevents",
      templated: false
    },
    beans: {
      href: "http://127.0.0.1:8080/actuator/beans",
      templated: false
    },
    caches-cache: {
      href: "http://127.0.0.1:8080/actuator/caches/{cache}",
      templated: true
    },
    caches: {
      href: "http://127.0.0.1:8080/actuator/caches",
      templated: false
    },
    health: {
      href: "http://127.0.0.1:8080/actuator/health",
      templated: false
    },
    health-component: {
      href: "http://127.0.0.1:8080/actuator/health/{component}",
      templated: true
    },
    health-component-instance: {
      href: "http://127.0.0.1:8080/actuator/health/{component}/{instance}",
      templated: true
    },
    conditions: {
      href: "http://127.0.0.1:8080/actuator/conditions",
      templated: false
    },
    shutdown: {
      href: "http://127.0.0.1:8080/actuator/shutdown",
      templated: false
    },
    configprops: {
      href: "http://127.0.0.1:8080/actuator/configprops",
      templated: false
    },
    env: {
      href: "http://127.0.0.1:8080/actuator/env",
      templated: false
    },
    env-toMatch: {
      href: "http://127.0.0.1:8080/actuator/env/{toMatch}",
      templated: true
    },
    info: {
      href: "http://127.0.0.1:8080/actuator/info",
      templated: false
    },
    loggers: {
      href: "http://127.0.0.1:8080/actuator/loggers",
      templated: false
    },
    loggers-name: {
      href: "http://127.0.0.1:8080/actuator/loggers/{name}",
      templated: true
    },
    heapdump: {
      href: "http://127.0.0.1:8080/actuator/heapdump",
      templated: false
    },
    threaddump: {
      href: "http://127.0.0.1:8080/actuator/threaddump",
      templated: false
    },
    metrics: {
      href: "http://127.0.0.1:8080/actuator/metrics",
      templated: false
    },
    metrics-requiredMetricName: {
      href: "http://127.0.0.1:8080/actuator/metrics/{requiredMetricName}",
      templated: true
    },
    scheduledtasks: {
      href: "http://127.0.0.1:8080/actuator/scheduledtasks",
      templated: false
    },
    httptrace: {
      href: "http://127.0.0.1:8080/actuator/httptrace",
      templated: false
    },
    mappings: {
      href: "http://127.0.0.1:8080/actuator/mappings",
      templated: false
    }
  }
}

安全

考虑到可能造成信息泄露等严重的安全隐患,可以使用security机制。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

引入这个依赖之后,所有的接口都访问不了了,需要填写用户名和密码。

spring:
  security:
    user:
      name: admin
      password: admin
      roles: ADMIN

management:
  server:
    port: 8081      

通过下面自定义Security配置类,可以对/actuator开始的url访问要求有ADMIN权限,其他的随意访问。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.antMatchers("/actuator/**").access("hasRole('ADMIN')")
				.antMatchers("/**").permitAll();
		super.configure(http);
	}
}

mybatis sql中使用<或<=时报错SAXParseException

使用mybatis时,当sql中使用<或<=时:

SELECT * FROM user WHERE age < 36
SELECT * FROM user WHERE age <= 36

会报错SAXParseException: 元素内容必须由格式正确的字符数据或标记组成。

org.apache.ibatis.builder.BuilderException: Error creating document instance.  Cause: org.xml.sax.SAXParseException; lineNumber: 7; columnNumber: 37; 元素内容必须由格式正确的字符数据或标记组成
	at org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:596) ~[mybatis-spring-2.0.2.jar:2.0.2]
	at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:475) ~[mybatis-spring-2.0.2.jar:2.0.2]
	at org.mybatis.spring.SqlSessionFactoryBean.getObject(SqlSessionFactoryBean.java:616) ~[mybatis-spring-2.0.2.jar:2.0.2]
	at org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.sqlSessionFactory(MybatisAutoConfiguration.java:180) ~[mybatis-spring-boot-autoconfigure-2.1.0.jar:2.1.0]
	at org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration$$EnhancerBySpringCGLIB$$9adef889.CGLIB$sqlSessionFactory$1(<generated>) ~[mybatis-spring-boot-autoconfigure-2.1.0.jar:2.1.0]
	at org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration$$EnhancerBySpringCGLIB$$9adef889$$FastClassBySpringCGLIB$$abadc28a.invoke(<generated>) ~[mybatis-spring-boot-autoconfigure-2.1.0.jar:2.1.0]
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration$$EnhancerBySpringCGLIB$$9adef889.sqlSessionFactory(<generated>) ~[mybatis-spring-boot-autoconfigure-2.1.0.jar:2.1.0]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_172]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_172]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_172]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_172]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	... 44 common frames omitted
Caused by: org.apache.ibatis.builder.BuilderException: Error creating document instance.  Cause: org.xml.sax.SAXParseException; lineNumber: 7; columnNumber: 37; 元素内容必须由格式正确的字符数据或标记组成
	at org.apache.ibatis.parsing.XPathParser.createDocument(XPathParser.java:260) ~[mybatis-3.5.2.jar:3.5.2]
	at org.apache.ibatis.parsing.XPathParser.<init>(XPathParser.java:126) ~[mybatis-3.5.2.jar:3.5.2]
	at org.apache.ibatis.builder.xml.XMLMapperBuilder.<init>(XMLMapperBuilder.java:80) ~[mybatis-3.5.2.jar:3.5.2]
	at org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:593) ~[mybatis-spring-2.0.2.jar:2.0.2]
	... 57 common frames omitted
Caused by: org.xml.sax.SAXParseException: 元素内容必须由格式正确的字符数据或标记组成
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:400) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1472) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.startOfMarkup(XMLDocumentFragmentScannerImpl.java:2635) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2732) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:505) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:842) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:771) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243) ~[na:1.8.0_172]
	at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339) ~[na:1.8.0_172]
	at org.apache.ibatis.parsing.XPathParser.createDocument(XPathParser.java:258) ~[mybatis-3.5.2.jar:3.5.2]
	... 60 common frames omitted

这是由于mybatis的SQL配置在XML中,一些特殊符号在XML解析时被转义。

像 “<” 和 “&” 字符在 XML 元素中都是非法的。
"<" 会产生错误,因为解析器会把该字符解释为新元素的开始。
"&" 会产生错误,因为解析器会把该字符解释为字符实体的开始。

方法一:使用转义字符

符号 转义符号
< &lt;
<= &lt;=
SELECT * FROM user WHERE age &gt;= 36

方法二:使用CDATA

SELECT * FROM user WHERE <![CDATA[ age < 36 ]]>

XML 文档中的所有文本均会被解析器解析。
只有 CDATA 区段中的文本会被解析器忽略。
CDATA 部分由 "<![CDATA[" 开始,由 "]]>" 结束:

maven加载本地jar包

添加引用,开发已经没有问题。

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>152</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/src/main/resources/lib/xxxx.jar</systemPath>
</dependency>

打包如果是springboot项目,只需要配上includeSystemScope即可:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <includeSystemScope>true</includeSystemScope>
            </configuration>
        </plugin>
    <plugins>
</build>

springboot整合mybatis

添加Maven依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>RELEASE</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>RELEASE</version>
</dependency>

在application.yml中配置数据源和mybatis

server:
  port: 8080

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/test1?jdbcCompliantTruncation=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

mybatis:
  mapper-locations: classpath:mapper/*.xml

mysql

CREATE TABLE `user` (
	`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(50) NULL DEFAULT NULL,
	`age` INT(11) UNSIGNED NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=3;

INSERT INTO `user` (`id`, `name`, `age`) VALUES (1, 'alita', 23);
INSERT INTO `user` (`id`, `name`, `age`) VALUES (2, 'sofia', 33);

DAO

├─main
│  ├─java
│  │  └─top
│  │      └─guoj
│  │          └─springboot
│  │              ├─controller
│  │              ├─dao
│  │              ├─entity
│  │              └─service
│  └─resources
│      └─mapper

UserDao.java

package top.guoj.springboot.dao;

import org.apache.ibatis.annotations.Mapper;

import java.util.List;
import java.util.Map;

@Mapper
public interface UserDao {
	List<Map<String, Object>> getUser();
}

UserDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="top.guoj.springboot.dao.UserDao">

    <select id="getUser" resultType="java.util.HashMap">
      select * from user;
    </select>

</mapper>

使用

package top.guoj.springboot.controller;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.guoj.springboot.dao.UserDao;

import java.util.List;
import java.util.Map;

@RestController
public class HelloController {

	@Autowired
	private UserDao userDao;

	@RequestMapping("/mybatis")
	String mybatis() {
		List<Map<String, Object>> users = userDao.getUser();
		return JSONObject.toJSONString(users);
	}
}

在配置application.yml文件中使用自定义属性

引入springboot依赖

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

自定义属性

在application.properties中配置自定义属性。

top.guoj.user-name=hello
top.guoj.pass-word=word

## OR
top.guoj.userName=hello
top.guoj.passWord=word

新建映射类

通过@ConfigurationProperties注解并配置prefix属性。

@Component
@ConfigurationProperties(prefix = "top.guoj")
public class Configure {

	private String userName;
	private String passWord;

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getPassWord() {
		return passWord;
	}

	public void setPassWord(String passWord) {
		this.passWord = passWord;
	}
}

使用

@RestController
public class HelloController {

	@Autowired
	private Configure configure;

	@RequestMapping("/configure")
	String configure() {
		return configure.getUserName() + configure.getPassWord();
	}
}

Dubbo配置

Dubbo配置可以使用XML、Properties、API、Annotation。

XML配置

Provider

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
	xmlns="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
	<dubbo:application name="demo-provider" />
	<dubbo:registry address="multicast://224.5.6.7:1234" />
	<bean id="helloService" class="top.guoj.provider.HelloServiceImpl" />
	<dubbo:service interface="top.guoj.api.HelloService" timeout="5000"
		ref="helloService" />
</beans>

Consumer

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
	xmlns="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
	<dubbo:application name="dubbo-consumer" />
	<dubbo:registry address="multicast://224.5.6.7:1234" />
	<dubbo:reference id="helloService" check="false" timeout="1500" retries="1"
		interface="top.guoj.api.HelloService" />
</beans>

Properties配置

API配置

Provider

HelloService helloService = new HelloServiceImpl();

ApplicationConfig application = new ApplicationConfig();
application.setName("dubbo-provider");

RegistryConfig registry = new RegistryConfig();
registry.setAddress("multicast://224.5.6.7:1234");

ServiceConfig<HelloService> service = new ServiceConfig<HelloService>(); 
service.setApplication(application);
service.setRegistry(registry);
service.setInterface(HelloService.class);
service.setRef(helloService);
//service.setVersion("1.0.0");

service.export();

System.out.println("Provider started.");
System.in.read();

Consumer

ApplicationConfig application = new ApplicationConfig();
application.setName("dubbo-consumer");

RegistryConfig registry = new RegistryConfig();
registry.setAddress("multicast://224.5.6.7:1234");

ReferenceConfig<HelloService> reference = new ReferenceConfig<>();
reference.setApplication(application);
reference.setRegistry(registry);
reference.setInterface(HelloService.class);
//reference.setVersion("1.0.0");

HelloService demoService = reference.get();
System.out.println(demoService.sayHello("world"));

provider与consumer的设置要保持一致。比如 provider设置了setVersion,consumer也需要同时设置,否则抛出异常:

Exception in thread "main" com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method sayHello in the service top.guoj.api.HelloService. Tried 3 times of the providers [192.168.100.61:20880] (1/1) from the registry 224.5.6.7:1234 on the consumer 192.168.100.61 using the dubbo version 2.6.2. Last error is: Failed to invoke remote method: sayHello, provider: dubbo://192.168.100.61:20880/top.guoj.api.HelloService?anyhost=true&application=dubbo-consumer&check=false&dubbo=2.6.2&generic=false&interface=top.guoj.api.HelloService&methods=sayHello&pid=9072&register.ip=192.168.100.61&remote.timestamp=1533266073861&side=consumer&timestamp=1533266624708, cause: com.alibaba.dubbo.remoting.RemotingException: Not found exported service: top.guoj.api.HelloService:20880 in [top.guoj.api.HelloService:1.0.0:20880], may be version or group mismatch , channel: consumer: /192.168.100.61:53628 --> provider: /192.168.100.61:20880, message:RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={dubbo=2.6.2, input=171, path=top.guoj.api.HelloService, interface=top.guoj.api.HelloService, version=0.0.0}]
com.alibaba.dubbo.remoting.RemotingException: Not found exported service: top.guoj.api.HelloService:20880 in [top.guoj.api.HelloService:1.0.0:20880], may be version or group mismatch , channel: consumer: /192.168.100.61:53628 --> provider: /192.168.100.61:20880, message:RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={dubbo=2.6.2, input=171, path=top.guoj.api.HelloService, interface=top.guoj.api.HelloService, version=0.0.0}]
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker(DubboProtocol.java:212)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:78)
	at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:96)
	at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:172)
	at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
	at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:80)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at java.lang.Thread.run(Thread.java:745)

	at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:102)
	at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:238)
	at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:75)
	at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:52)
	at com.alibaba.dubbo.common.bytecode.proxy0.sayHello(proxy0.java)
	at top.guoj.consumer.Consumer.main(Consumer.java:37)
Caused by: com.alibaba.dubbo.remoting.RemotingException: com.alibaba.dubbo.remoting.RemotingException: Not found exported service: top.guoj.api.HelloService:20880 in [top.guoj.api.HelloService:1.0.0:20880], may be version or group mismatch , channel: consumer: /192.168.100.61:53628 --> provider: /192.168.100.61:20880, message:RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={dubbo=2.6.2, input=171, path=top.guoj.api.HelloService, interface=top.guoj.api.HelloService, version=0.0.0}]
com.alibaba.dubbo.remoting.RemotingException: Not found exported service: top.guoj.api.HelloService:20880 in [top.guoj.api.HelloService:1.0.0:20880], may be version or group mismatch , channel: consumer: /192.168.100.61:53628 --> provider: /192.168.100.61:20880, message:RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={dubbo=2.6.2, input=171, path=top.guoj.api.HelloService, interface=top.guoj.api.HelloService, version=0.0.0}]
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker(DubboProtocol.java:212)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:78)
	at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:96)
	at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:172)
	at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
	at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:80)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at java.lang.Thread.run(Thread.java:745)

	at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.returnFromResponse(DefaultFuture.java:222)
	at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:139)
	at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:112)
	at com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:95)
	at com.alibaba.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:148)
	at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:77)
	at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:54)
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
	at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75)
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
	at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:48)
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:72)
	at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
	at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:78)
	... 5 more