quartz集群配置

JobStore

在开始配置quartz集群之前,先要了解一下Scheduler的“工作数据”的存放点:

RAMJobStore

RAMJobStore是最简单最高效,也是quartz默认的存储方式,它将“工作数据”存放在内存中,所以它无需任何额外配置,也是最高效的原因。但是这种方式的弊端也是显而易见的:

  • 任务量受限于内存大小
  • 当系统崩溃时,就丢掉了所有任务信息,这对于一些程序是灾难性的
  • 无法集群化

JDBCJobStore

看到JDBC我们就应该已经知道了,这种方式是将“工作数据”存储到了数据库中并且广泛适用于所有数据库,这既有利:

  • 任务量没有限制
  • 持久化任务信息
  • 可集群化
  • 失败转移(fail-over, 集群化情况下,其中一台系统崩溃会自动将任务转移到其他可用系统上)

也有弊:

  • 没有RAMJobStore方式高效
  • 增大数据库压力

相比之下,利远大于弊,所以一般使用quartz框架都使用JDBCJobStore这种方式。

两种事务管理方式

quartz提供了两种事务管理方式:

JobStoreTX

quartz框架自己管理事务,这是最常用的方式

JobStoreCMT

将quartz框架的事务集成到J2EE容器事务中。

quartz.properties

程序启动时,会默认读取classpath下的quartz.properties,所以我们将配置都写在此文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 在同一个项目中name不同(用来区分scheduler), 在集群中name相同
quartz.scheduler.instanceName=myScheduler
#org.quartz.jobStore.dataSource=myDS
#org.quartz.dataSource.myDS.driver=com.mysql.cj.jdbc.Driver
#org.quartz.dataSource.myDS.URL=jdbc:mysql://localhost:3306/quartzdb?useUnicode=true&characterEncoding=utf8
#org.quartz.dataSource.myDS.user=root
#org.quartz.dataSource.myDS.password=root
#org.quartz.dataSource.myDS.maxConnections=10

# 可以为任意字符串, 但必须保持唯一, AUTO 自动生成ID, SYS_PROP使用系统环境变量${org.quartz.scheduler.instanceId}
org.quartz.scheduler.instanceId=AUTO
# 是否跳过更新检查
org.quartz.scheduler.skipUpdateCheck=true
# 是否打开quartz的JMX支持
org.quartz.scheduler.jmx.export=true
# jobStore类配置
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# 通过JDBC访问数据库的代理类
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# quartz数据表前缀
# org.quartz.jobStore.tablePrefix=T_B_QRTZ_
# 是否使用集群
org.quartz.jobStore.isClustered=true
# 集群状态的更新时间间隔
org.quartz.jobStore.clusterCheckinInterval=20000
# 最大错误重试次数
org.quartz.jobStore.maxMisfiresToHandleAtATime=1
# 在Trigger被认为是错误触发之前, scheduler还容许Trigger通过它的下次触发时间的秒数, 默认60000
org.quartz.jobStore.misfireThreshold=120000
# 调用setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE)方法设置事务级别
org.quartz.jobStore.txIsolationLevelSerializable=true
# 必须为一个sql字符串, 查询locks表里的一行, {0}被覆盖为tablePrefix ? 被覆盖为scheduler's name
org.quartz.jobStore.selectWithLockSQL=SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE

# 使用的线程池实例
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# 线程池中线程数, 很大取决于任务的性质和系统资源
org.quartz.threadPool.threadCount=10
# 线程的优先级, 介于Thread.MIN_PRIORITY(1)和Thread.PRIORITY(10), 默认Thread.NORM_PRIORITY(5)
org.quartz.threadPool.threadPriority=5
# 加载任务代码的ClassLoader是否从外部继承
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
# trigger历史记录插件
org.quartz.plugin.triggHistory.class=org.quartz.plugins.history.LoggingJobHistoryPlugin
# shutdown-hook 插件捕捉JVM关闭时间并调用scheduler的shutdown方法
org.quartz.plugin.shutdownhook.class=org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownhook.cleanShutdown=true

在这里我注释掉了关于数据库连接信息的配置,决定在quartConfig.java文件中注入dataSource。

SpringJobFactoryConfig.java

在Job执行类中,无法注入Spring容器中的bean,因为Spring容器和quartz容器是隔离开的,所以我们需要使用Spring提供的AutowireCapableBeanFactory(可装配applicationContext之外的bean)和重写AdaptableJobFactory中的createJobInstance方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* TODO 自定义JobFactory, 为了注入jobInstance中的@Autowired @Resource等实例
* Created by jinzili on 25/09/2017.
*/
@Component
public class SpringJobFactoryConfig extends AdaptableJobFactory{

// 装配applicationContext管理之外的bean
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;

@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
// 装配jobInstance
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}

QuartzConfig.java

将自定义的JobFactory配置到SchedulerFactoryBean和Scheduler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class QuartzConfig {

@Autowired
private SpringJobFactoryConfig springJobFactory;

@Qualifier("dataSource")
@Autowired
private DataSource dataSource;

@Bean
public SchedulerFactoryBean schedulerFactoryBean(){
SchedulerFactoryBean factory = new SchedulerFactoryBean();
// 是否覆盖已存在的job
factory.setOverwriteExistingJobs(true);
// 配置数据源
System.out.println(dataSource);
factory.setDataSource(dataSource);
// 启动延时
factory.setStartupDelay(10);
// 是否自动启动
factory.setAutoStartup(true);
// factory.setQuartzProperties(this.quartzProperties());
factory.setApplicationContextSchedulerContextKey("applicationContext");
// 配置自定义JobFactory
factory.setJobFactory(springJobFactory);
return factory;
}

@Bean
public Scheduler scheduler(){
return schedulerFactoryBean().getScheduler();
}

}

在其他bean中通过注入scheduler来增删改查Job。

至此,一个有可持久化,集群化,失败转移的功能的quartz框架就搭建好了。

评论

quartz基础详解

quartz基础知识

Job

要想让quartz来管理任务,就必须要遵守quartz的规。Job类需要实现org.quartz.Job接口,重写接口中唯一的exceute(JobExecutionContext jec)方法,在这个方法中实现具体的业务逻辑。JobExecutionContext保存了任务执行时的上下文,比如执行的是哪个trigger,也包括了执行时的数据信息等。定义一个Job:

1
2
3
4
5
6
7
8
public static class JobImpl implements Job{
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// do some thing...
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date()));
}
}

JobDetail

JobDetail接口定义了用来描述一个job所有的属性信息,下面列出一些核心属性:

属性 说明
class Job实现类,jobDetail根据此获取Job实例
name Job名称,同一Job组中名称唯一
group Job组名, 同一scheduler容器中组名唯一
description Job描述信息
durability 是否持久化。如果非持久化,当没有trigger与之关联,则会被自动删除。
shouldRecover 是否可恢复。如果可恢复,那么job执行中,scheduler崩溃,当scheduler重启后,此job会被重新执行。
jobDataMap 可把任意kv数据存入此属性,支持序列化实现了Serialiable接口,key=String, value=Object。

定义一个JobDetail:

1
2
3
4
// 定义一个job
JobDetail jobDetail = JobBuilder.newJob(JobImpl.class)
.withIdentity("myJob", "myJobGroup")
.build();

Trigger

quartz提供了多种触发器,他们是Trigger的具体实现。其中经常使用的有两种:SimpleTrigger和CronTrigger。

SimpleTrigger

SimpleTrigger包含几个特点:开始时间、结束时间、重复次数和重复间隔。结束时间会覆盖重复次数,比如某触发器已到结束时间但是未达到重复次数也会停止执行。如果任务符合这种工作性质,那么使用SimpleTrigger这种触发器最适合。定义一个SimpleTrigger:

1
2
3
4
5
6
7
8
 // 定义一个trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "myTriggerGroup")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();

这个Trigger立即开始并每5秒无限循环执行。
SimpleTrigger提供了许多执行策略,比如:未来5分钟开始执行、偶数小时执行等,需要在不同业务逻辑中不同配置。
自定义失败重试策略

策略 含义
REPEAT_INDEFINITELY 不断重复直到结束时间戳
MISFIRE_INSTRUCTION_FIRE_NOW 如果失败立即再次触发
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 如果失败立即再次触发,重试次数+1,遵守结束时间戳
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT 如果失败立即再次触发,遵守结束时间戳
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT 如果失败,则到下次触发时间再执行

CronTrigger

CronTrigger触发器用的更多一下,这个触发器基于日历的概念,而不是具体开始时间,具体结束时间,重复次数等。你可以指定每周一凌晨2点执行,每月每周一执行等复杂逻辑。使用此触发器需要了解Cron表达式,这里不在赘述。定义一个CronTrigger:

1
2
3
4
5
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("helloworld-trigger-name-1", "helloworld-trigger-group-1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * ? * *"))
.startNow()
.build();

自定义失败重试策略

策略 含义
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 如果失败立即再次触发
MISFIRE_INSTRUCTION_DO_NOTHING 如果失败则到下次执行时间再次触发

Scheduler容器

一个Scheduler容器对应一个quartz独立的运行容器,在Scheduler容器中,将Trigger绑定到JobDetail上,由Scheduler调度执行。

一个Trigger对应一个JobDetail,一个JobDetail可对应多个Trigger。

Scheduler容器以键值对的形式保存了任务执行时的上下文信息,还提供了多个接口方法,允许外部通过组名及任务名或触发器名访问容器中的JobDetail或Trigger,定义一个Scheduler:

1
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

注册一个可执行的任务到Scheduler容器中:

1
scheduler.scheduleJob(jobDetail, trigger);

一个可执行的任务需要一个JobDetail和一个Trigger,这个很好理解,一个任务需要任务的描述信息,和一个触发任务执行的策略。quartz将任务和触发器解耦,这样我们就可以实现一个任务对应多个触发器。

一个简单demo

因为quartz的是多线程执行的,所以我们在main方法里测试而不是使用JUnit。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* TODO quartz simple demo
* Created by jinzili on 17/10/2017.
*/
public class QuartzController {

public static class JobImpl implements Job{

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date()));
}

}

public static void main(String[] args) throws SchedulerException{
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

// 定义一个jobDetail
JobDetail jobDetail = JobBuilder.newJob(JobImpl.class)
.withIdentity("myJob", "myJobGroup")
.build();

// 定义一个trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "myTriggerGroup")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();


scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}

}

评论

Spring Data JPA中的nativeQuery和pageable

在Sprint Data JPA中默认提供了许多CRUD方法,基本上单表操作已经完全够用了,但是在开发过程中发现使用nativeQuery不能和pageable共用,例如

1
2
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
Page<User> findByEmailAddress(String emailAddress, Pageable pageable);

此时项目启动时会报:

1
org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination in method

意思就是同一个方法中native queries 不能和 sorting、pagination共用。
但是这种需求却是实实在在的存在的,查看Spring Data JPA官方文档,给出了一下解决方式:

1
2
3
4
@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);

使用此方式后依然报上文的exception,查阅其他资料后使用如下方式解决:

1
2
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1 \n#pageable\n", nativeQuery = true)
Page<User> findByEmailAddress(String emailAddress, Pageable pageable);

在sql一句后加上 \n#pageable\n

这很有可能是Spring Data JPA的一个bug,并且在Spring的jira上也发现了这个issue
https://jira.spring.io/browse/DATAJPA-928

都是泪。

评论

Java发送Http请求:HttpClientUtils

  • 添加依赖
1
2
3
4
5
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
  • 导入包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
  • 核心代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* ClassName: HttpClientUtil.java
* FUNCTION:
* @author: zili
* @version:
* @Date: Oct 9, 2017
*/
public class HttpClientUtils {

private final static Integer DEFAULT_TIMEOUT = 10000;

public static HttpResponse doGet(String url, Map<String, String> param, Integer timeout) {

// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();

HttpResponse httpResponse = null;
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();

// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
if(timeout != null && timeout > 0){
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(timeout)
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
httpGet.setConfig(requestConfig);
}
// 执行请求
response = httpclient.execute(httpGet);
httpResponse = new HttpResponse();
httpResponse.setResponseCode(response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
httpResponse.setResponseData(EntityUtils.toString(response.getEntity(), "UTF-8"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return httpResponse;
}

public static HttpResponse doGet(String url) {
return doGet(url, null, DEFAULT_TIMEOUT);
}

public static HttpResponse doPost(String url, Map<String, String> param, Integer timeout) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
HttpResponse httpResponse = null;
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, "utf-8");
httpPost.setEntity(entity);
}
if(timeout != null && timeout > 0){
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(timeout)
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
httpPost.setConfig(requestConfig);
}
// 执行http请求
response = httpClient.execute(httpPost);
httpResponse = new HttpResponse();
httpResponse.setResponseCode(response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
httpResponse.setResponseData(EntityUtils.toString(response.getEntity(), "UTF-8"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

return httpResponse;
}

public static HttpResponse doPost(String url) {
return doPost(url, null, DEFAULT_TIMEOUT);
}

public static HttpResponse doPostJson(String url, String json, Integer timeout) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
HttpResponse httpResponse = null;
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
if(timeout != null && timeout > 0){
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(timeout)
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
httpPost.setConfig(requestConfig);
}
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
httpResponse = new HttpResponse();
httpResponse.setResponseCode(response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
httpResponse.setResponseData(EntityUtils.toString(response.getEntity(), "UTF-8"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

return httpResponse;
}

public static class HttpResponse implements Serializable{

/**
*
*/
private static final long serialVersionUID = -2033460329795125474L;

private Integer responseCode;

private String responseData;

// 省略getter setter

}

}
评论

让centos7中的docker飞过墙

安装shadowsocks

yum install -y epel-release python-pip

pip install shadowsocks

vim /etc/shadowsocks.json

{
“server”: “your.vpn.com”,
“server_port”: 8388,
“password”: “pwd”,
“method”: “aes-256-cfb”,
“local_address”:”127.0.0.1”,
“local_port”:1080
}

客户端启动命令(server、password、methond是你购买的ss账号提供的服务)

sslocal -c /etc/shadowsocks.json

安装privoxy

yum install -y privoxy

vi /etc/privoxy/config,注释掉listen-address行,在最后加入两行

forward-socks5t / 127.0.0.1:1080 .
listen-address 127.0.0.1:8118

vi ~/.bashrc

export http_proxy=http://127.0.0.1:8118
export https_proxy=http://127.0.0.1:8118
export ftp_proxy=http://127.0.0.1:8118

source ~/.bashrc

systemctl restart privoxy,重启privoxy

curl http://www.google.com,查看是否可以访问

docker pull gcr.io/google_containers/kube-apiserver-amd64:v1.6.1 试试吧

评论

安装kubernetes-dashboard插件

关于如何搭建基于docker的Kubernetes环境,请看笔者另一片博客:
https://my.oschina.net/u/3559870/blog/1031428

插件说明

完成使用Kubeadm搭建Kubernetes(docker)之后,Kubernetes实际已经搭建成功~但是频繁操作命令行界面是会崩溃的好吗!!所以在Kubernetes 1.2版本后新增了Kube Dashboard,我们就可以在浏览器愉快的通过点 点 点操作啦。

##功能说明 ##
create:上传json或者yaml的方式新建resource,同kubectl create -f
delete:删除副本(Replication Controllers)
modify:修改副本数(replicas)
query:查询相关信息,同kubectl get
通过web-ui+上述功能,我们就能基本脱离命令行界面了!

安装步骤

此时已经完成kubernetes的搭建。
我们将dashboard以静态Pod的方式运行在Master Node上。

1
cd /etc/kubernetes/manifests

此目录是已有的静态Pod的yaml文件,我们创建kubernetes-dashboard.yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Configuration to deploy release version of the Dashboard UI compatible with
# Kubernetes 1.6 (RBAC enabled).
#
# Example usage: kubectl create -f <this_file>

apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
labels:
app: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kube-system
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
labels:
app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: kubernetes-dashboard
template:
metadata:
labels:
app: kubernetes-dashboard
spec:
containers:
- name: kubernetes-dashboard
image: registry.cn-beijing.aliyuncs.com/bbt_k8s/kubernetes-dashboard-amd64:v1.6.0
imagePullPolicy: Always
ports:
- containerPort: 9090
protocol: TCP
args:
# Uncomment the following line to manually specify Kubernetes API server Host
# If not specified, Dashboard will attempt to auto discover the API server and connect
# to it. Uncomment only if the default does not work.
# - --apiserver-host=http://my-address:port
livenessProbe:
httpGet:
path: /
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
serviceAccountName: kubernetes-dashboard
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
---
kind: Service
apiVersion: v1
metadata:
labels:
app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
type: NodePort
ports:
- port: 80
targetPort: 9090
selector:
app: kubernetes-dashboard

创建文件kubernetes-dashboard-rbac.yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: dashboard-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: default
namespace: kube-system

创建dashboard、dashboard-rbac resource

1
2
kubectl create -f kubernetes-dashboard-rbac.yml
kubectl create -f kubernetes-dashboard.yaml

创建成功结果如下所示

1
2
3
4
5
6
7
[root@kube-master manifests]# kubectl create -f dashboard-rbac.yaml
clusterrolebinding "dashboard-admin" created
[root@kube-master manifests]# kubectl create -f dashboard.yaml
serviceaccount "kubernetes-dashboard" created
clusterrolebinding "kubernetes-dashboard" created
deployment "kubernetes-dashboard" created
service "kubernetes-dashboard" created

查询dashboard运行的端口

1
kubectl describe --namespace kube-system service kubernetes-dashboard
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@kube-master manifests]# kubectl describe --namespace kube-system service kubernetes-dashboard
Name: kubernetes-dashboard
Namespace: kube-system
Labels: app=kubernetes-dashboard
Annotations: <none>
Selector: app=kubernetes-dashboard
Type: NodePort
IP: 10.110.236.54
Port: <unset> 80/TCP
NodePort: <unset> 30989/TCP
Endpoints: 10.244.0.16:9090
Session Affinity: None
Events: <none>

NodePort就是我们要访问的端口啦!快快打开浏览器输入{master-ip}:{node-port}吧!

评论

快速搭建基于docker的kubernetes集群

##版本说明 ##

  1. kubernetes1.6
  2. docker1.12.6

环境准备

192.168.0.51 master
192.168.0.52 minion1
192.168.0.53 minion2

##安装docker ##

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装yum-utils 管理yum repository及扩展包的工具
yum install -y yum-utils
# 增加docker repository
yum-config-manager \
--add-repo \
https://docs.docker.com/v1.13/engine/installation/linux/repo_files/centos/docker.repo
# 下载包信息到本地
yum makecache fast
# 安装docker-1.12.6
yum install -y docker-engine-1.12.6
# 启动docker服务
systemctl start docker
# 开机启动docker
systemctl enable docker

##系统配置 ##
创建/etc/sysctl.d/k8s.conf文件,内容为:

1
2
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1

执行

1
sysctl -p /etc/sysctl.d/k8s.conf

在/etc/hostname中修改各节点的hostname,在/etc/hosts中设置hostname对应非lo回环网卡ip:

1
2
3
192.168.0.51 master
192.168.0.52 minion1
192.168.0.53 minion2

##安装kubeadm和kubelet ##
安装kubeadm和kubelet需要技术梯子,添加以下
1、通过修改/etc/hosts文件添加IP *.google.com,比如

1
2
3
4
5
6
7
216.58.200.33  gcr.io
216.58.200.33 www.gcr.io
216.58.200.33 cloud.google.com
216.58.200.33 packages.cloud.google.com
216.58.200.33 console.cloud.google.com
216.58.200.33 status.cloud.google.com
216.58.200.33 ssh.cloud.google.com

2、:

1
cat <<EOF > /etc/yum.repos.d/kubernetes.repo

输入如下内容:

1
2
3
4
5
6
7
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
EOF

安装kubelet,kubeadm,kubectl

1
2
yum -y install socat kubelet-1.6.1 kubeadm-1.6.1 kubectl-1.6.1
systemctl enable kubelet.service

##初始化集群 ##
上面的步骤每个node都需要执行,此步骤只在Master Node 执行。选择192.168.0.51这台作为Master Node,在此机器上

1
kubeadm init --kubernetes-version=v1.6.1 --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.0.51

选择flannel作为网络插件,所以上面的命令指定–pod-network-cidr=10.244.0.0/16。初始化遇到问题时,使用下面的命令清理然后再初始化:

1
2
3
4
5
6
kubeadm reset
ifconfig cni0 down
ip link delete cni0
ifconfig flannel.1 down
ip link delete flannel.1
rm -rf /var/lib/cni/

不出意外Master Node 已初始化成功。此时查看节点状态 kubectl get nodes 会报 The connection to the server localhost:8080 was refused - did you specify the right host or port? 我们查看kube-apiserver的监听端口

1
2
[root@kube-master ~]# netstat -nltp | grep apiserver
tcp6 0 0 :::6443 :::* LISTEN 5430/kube-apiserver

我们发现apiserver只监听了6443端口,但是我们需要使用kubectl访问apiserver,so 在~/.bash_profile追加下面的环境变量

1
2
3
export KUBECONFIG=/etc/kubernetes/admin.conf
# 使环境变量生效
source ~/.bash_profile

现在再试试kubectl get nodes 吧!

##安装Pod Network ##
安装flannel network add-on
分两种情况

1、只有一张网卡
直接执行

1
2
kubectl create -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel-rbac.yml
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

2、有多张网卡
执行

1
kubectl create -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel-rbac.yml

执行第二步有所不同,下载https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml ,flanneld启动参数加上–iface=[iface-name]

1
2
3
4
5
6
7
8
9
10
11
......
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-flannel-ds
......
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.7.0-amd64
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr", "--iface=eth1" ]
......

eth1这里换成自己的网卡名即可。创建flanneld

1
2
3
kubectl create -f {修改后的kube-flannel.yml路径}
# 确保所有的Pod都处于Running状态
kubectl get pod --all-namespaces -o wide

##使Master Node 参与工作负载 ##
一般不建议将Master Node参与负载,此时只为测试环境方便。

1
2
# 使Master Node参与工作负载
kubectl taint nodes --all node-role.kubernetes.io/master-

##测试 ##

1
2
3
kubectl run curl --image=radial/busyboxplus:curl -i --tty
If you don't see a command prompt, try pressing enter.
[ root@curl-57077659-xgckw:/ ]$

进入后执行nslookup kubernetes.default确认DNS解析正常。

1
2
3
4
5
6
[ root@curl-57077659-xgckw:/ ]$ nslookup kubernetes.default
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name: kubernetes.default
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local

如果正常解析,就大功告成啦!

1
2
# 删除curl这个Pod
kubectl delete deploy cutl

##向集群中添加节点 ##
根据上面的步骤安装docker,kubelet套件。
在Master Node节点上执行

1
2
# 获取token
kubeadm token list

在minion1上执行

1
2
3
4
5
6
# 关闭防火墙
systemctl stop firewalld
# 禁止防火墙开机启动
systemctl disable firewalld
# 添加节点
kubeadm join --token ${token} ${master-ip}:6443

看到如下内容就说明我们已经成功向集群中添加节点了!nice

1
2
3
4
5
6
7
8
kubeadm join --token a432c6.078b144b659c82b4 192.168.0.51:6443
----- 略略略 ----
Node join complete:
* Certificate signing request sent to master and response
received.
* Kubelet informed of new secure connection details.

Run 'kubectl get nodes' on the master to see this machine join.

在Master Node上执行kubectl get nodes 确保所有的node是Ready的状态。

##问题总结 ##
Q:Minion Node一直处于notReady状态?

A:主要有两个原因:

1、启动kubelet的时候,会pull两个镜像(gcr.io/**),因为GFW的存在,不能成功pull,所以要自己找到这两个docker镜像

1
2
gcr.io/google_containers/pause-amd64:3.0
gcr.io/google_containers/kube-proxy-amd64:v1.6.1

2、 使用Kubeadm工具搭建的Kubernetes集群,已经默认集成了安全策略,所以要将Master Node节点/etc/kubernetes/pki下的所有文件复制到Minion Node相同目录下一份。所以在Master Node上执行

1
scp /etc/kubernetes/pki/* root@{minion-ip}:/etc/kubernetes/pki

参考:
http://blog.frognew.com/2017/04/kubeadm-install-kubernetes-1.6.html
http://www.iyunv.com/thread-383770-1-1.html
http://www.infoq.com/cn/articles/Kubernetes-system-architecture-introduction

评论

在web项目中应用Mybatis

mybatis环境准备

  1. 加入所需jar包

    工程结构

    所需jar包,博主有上传资源
    http://download.csdn.net/detail/jinzili777/9480604

在开始之前我们有必要了解mybatis执行流程:

①SqlMapConfig.xml(是mybatis的全局配置文件,名称不固定的)配置了数据源、事务等mybatis运行环境、配置映射文件(配置sql语句)
mapper.xml(映射文件)、mapper.xml、mapper.xml…..

②SqlSessionFactory(会话工厂),根据配置文件创建工厂
作用:创建SqlSession

③SqlSession(会话),是一个接口,面向用户(程序员)的接口
作用:操作数据库(发出sql增、删、改、查)

④Executor(执行器),是一个接口(基本执行器、缓存执行器)
作用:SqlSession内部通过执行器操作数据库

⑤mapped statement(底层封装对象)
作用:对操作数据库存储封装,包括 sql语句,输入参数、输出结果类型

SqlMapConfig.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 加载属性文件 -->
<properties resource="db.properties">
<!-- properties中还可以配置一些属性名和属性值,但不建议在这里添加任何属性 -->
</properties>
<!-- 全局配置 -->
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载即按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 别名定义 -->
<typeAliases>
<!--
type:类型路径
alias:别名

<typeAlias type="cn.jzl.mybatis.po.User" alias="user"/>
-->
<!-- 批量别名定义
指定包名:mybatis自动扫描包中的po类,自动定义别名
别名就是类名(首字母大小写都可以)
-->
<package name="cn.jzl.mybatis.po"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="sqlmap/User.xml" />
<!-- 加载单个mapper
<mapper resource="mapper/UserMapper.xml" />-->
<!-- mapper接口加载
<mapper class="cn.jinzili.mybatis.mapper.UserMapper"/>
-->
<!-- 批量加载 -->
<package name="cn.jinzili.mybatis.mapper"/>
</mappers>
</configuration>

db.properties

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=111111

log4j.properties

1
2
3
4
5
6
7
8
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

mybatis开发dao

  1. 开发原始dao的方法
    ①dao接口
    1
    2
    3
    4
    public interface UserDao {
    //根据id查询用户信息
    public User findUserById(int id) throws Exception;
    }

②dao接口实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserDaoImpl implements UserDao{

//需要向dao实现类中SqlSessionFactory
//通过构造函数注入
private SqlSessionFactory sqlSessionFactory;
public UserDaoImpl(SqlSessionFactory sqlSessionFactory){
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User findUserById(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.close();
return user;
}
}

③测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserDaoImplTest {
//创建sqlSessionFactory
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws Exception{
//加载mybatis配置文件
String resource = "SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂,传入mybatis的配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception {
//创建UserDao的对象
UserDao userDao = new UserDaoImpl(sqlSessionFactory);
//调用UserDao的方法,并打印
System.out.println(userDao.findUserById(1));
}

}

测试结果

  1. mapper代理方法
    ①mapper.java
1
2
3
public interface UserMapper {
public User findUserById(int id) throws Exception;
}

②mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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">

<!--
namespace命名空间,作用就是对sql进行分类管理,理解为sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用
-->
<mapper namespace="cn.jinzili.mybatis.mapper.UserMapper">
<!-- 在映射文件中配置很多sql语句 -->
<!--
通过select执行数据库查询
id:标识映射文件中的sql,将sql语句封装到mappedStatement对象中,称为statement的id
parameterType:指定输入参数的类型 ,这里指定int型
#{id}:其中的id标识接收输入的参数,参数名称是id,如果输入参数是简单类型,#{}中的参数吗可以任意
resultType:指定sql输出结果的所映射的java对象类型,select指定resultType表示将单条记录映射成的java对象
-->
<select id="findUserById" parameterType="int" resultType="user">
SELECT * FROM USER WHERE id=#{id}
</select>
</mapper>

③在SqlMapConfig.xml文件加载mapper.xml

1
2
3
4
5
6
7
8
9
10
11
<!-- 加载映射文件 -->
<mappers>
<mapper resource="sqlmap/User.xml" />
<!-- 加载单个mapper
<mapper resource="mapper/UserMapper.xml" />-->
<!-- mapper接口加载
<mapper class="cn.jinzili.mybatis.mapper.UserMapper"/>
-->
<!-- 批量加载,建议用此方法 -->
<package name="cn.jinzili.mybatis.mapper"/>
</mappers>

④测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws Exception{
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);
sqlSession.close();
System.out.println(user);
}
}


如果mapper方法返回单个pojo对象(非集合对象),代理对象内部通过selectOne查询数据库。
如果mapper方法返回集合对象,代理对象内部通过selectList查询数据库。

总结

在此博客中,只介绍了mybatis最简单的使用方法,适合初学者快速入门,但是想要深入了解mybatis,还需要大家一起努力(推荐看一些mybatis的培训视频)。

评论

在web项目中应用SpringMVC

什么是springmvc

springmvc是spring框架的一个模块,springmvc和spring无需通过中间整个层进行整合,它是一个基于mvc的web框架。

springmvc与struts2的区别

  1. springmvc是基于方法开发的,struts2是基于类开发的。springmvc将url和controller方法映射,映射成功后springmvc生成一个Handler对象(也就是controller),对象中只包括了映射的method,方法执行结束后,形参数据销毁。
  2. springmvc可以进行单例开发,并且建议使用单例开发,struts2只能多例开发(struts2通过类成员变量接收数据,多个线程中的数据可能不一样,所以不能使用单例开发)。
  3. 经过实际的测试,struts2速度慢,是因为使用了struts标签,所以在使用struts2进行开发的时候,建议使用jstl。

springmvc框架执行流程

springmvc框架执行流程

用入门程序来学习springmvc

springmvc运行环境

所有jar包
jar包下载地址(mybatis+spring(包括springmvc)所有jar包):
http://download.csdn.net/detail/jinzili777/9480604

配置前端控制器

在web.xml文件中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- springmvc前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--
contextConfigLocation配置springmvc加载的配置文件路径(配置映射器,适配器等)
如果不配置此属性,默认加载的是/WEB-INF/servlet名称-servlet.xml(在这里就是上面<servlet-name>的值:springmvc-servlet.xml)
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
第一种:*.action,访问以*.action结尾的由DispatcherServlet解析
第二种:/,所有的访问都由DispatcherServlet解析,如要访问静态资源(js,css...)需要配置不让DispatcherServlet
解析,此方法可以实现RESTurl风格的url
第三种:/*,这种配置是不正确的,使用此方法,当我们要转发到一个jsp页面时也会由DispatcherServlet解析,会报错
-->
<url-pattern>*.action</url-pattern>
</servlet-mapping>

非注解的处理器映射器和适配器

在classpath下的springmvc.xml中

不使用注解的处理器适配器
不使用注解的处理器适配器的配置

此方法只能执行实现了Controller接口的Handler,下面是一个小demo

开发Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ItemsController1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {

//调用service查找 数据库,查询商品列表,这里使用静态数据模拟
List<Items> itemsList = new ArrayList<Items>();
//向list中填充静态数据

Items items_1 = new Items();
items_1.setName("联想笔记本");
items_1.setPrice(6000f);
items_1.setDetail("ThinkPad T430 联想笔记本电脑!");

Items items_2 = new Items();
items_2.setName("苹果手机");
items_2.setPrice(5000f);
items_2.setDetail("iphone6苹果手机!");

itemsList.add(items_1);
itemsList.add(items_2);

//返回ModelAndView
ModelAndView modelAndView = new ModelAndView();
//相当 于request的setAttribut,在jsp页面中通过itemsList取数据
modelAndView.addObject("itemsList", itemsList);

//指定视图
modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp");
return modelAndView;
}

setViewName()方法中是转发到的jsp页面,页面这里不再赘述,在这个jsp页面可以取到request域中的itemsList。
在spring容器加载Handler

1
<bean name="/queryItems.action" class="cn.jzl.ssm.controller.ItemsController1"></bean>

配置不使用注解的处理器映射器

1
2
3
4
<!-- 
将bean的name作为url进行查找,需要在配置Handler时指定beanname(就是url)
-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

配置视图解析器

1
2
3
4
5
6
7
8
<!-- 视图解析器 
解析jsp,默认使用jstl,classpath下得有jstl的包
jsp路径的前缀和jsp路径的后缀
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>

注解的处理器映射器和适配器

在spring3.1之前使用
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping注解映射器。

在spring3.1之后使用
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping注解映射器。

在spring3.1之前使用
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter注解适配器。

在spring3.1之后使用
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter注解适配器。

配置注解映射器和适配器

1
2
3
4
<!-- 注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />
<!-- 注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />

开发注解Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//使用Controller标识 它是一个控制器
@Controller
public class ItemsController {
@Resource
private ItemsService itemsService;
//@RequestMapping实现 对queryItems方法和url进行映射,一个方法对应一个url
//一般建议将url和方法写成一样
@RequestMapping("/queryItems")
public ModelAndView queryItems(ItemsQueryVo itemsQueryVo) throws Exception{
/*
业务逻辑
*/
List<ItemsCustom> itemsList = itemsService.findItemsList(itemsQueryVo);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemsList", itemsList);
modelAndView.setViewName("items/itemsList");
return modelAndView;
}
}

@controller注解必须要加,作用标识类是一个Handler处理器。
@requestMapping注解必须要加,作用:
1、对url和Handler的方法进行映射。
2、可以窄化请求映射,设置Handler的根路径,url就是根路径+子路径请求方式
3、可以限制http请求的方法
映射成功后,springmvc框架生成一个Handler对象,对象中只包括 一个映射成功的method。

在spring容器中加载Handler

1
2
3
4
<!-- 对于注解的Handler可以单个配置 -->
<!-- <bean class="cn.jzl.ssm.controller.ItemsController"/> -->
<!-- 但是在开发中,建议使用扫描 -->
<context:component-scan base-package="cn.jzl.ssm.controller" />

配置视图解析器方法不变

使用mvc:annotation-driven

配置映射器和适配器
使用如下配置,可以代替第二种方法中注解的适配器和映射器

1
<mvc:annotation-driven></mvc:annotation-driven>

开发注解Handler

与第二种方法开发方法一致

配置视图解析器方法不变

小结

在学习并使用springmvc的过程中,了解其执行流程是非常重要的。在实际使用过程中会碰到各式各样的问题,也不是一篇博客或一部视频能够介绍完全的,所以学习没有捷径,只有通过一行行的代码累加、沉淀,多敲几行代码,理解就会加深几分。共勉。

评论

解决js跨域的问题

什么是跨域?

Js为了安全有一个限制,不允许跨域访问。
1、如果两个url的域名不同
2、Url相同,端口不同也是跨域
3、Ip不同也是跨域

解决方法

方法的原理

可以使用jsonp解决跨域的问题。
1、在js中不能跨域请求数据,js可以跨域请求js片段。
2、可以把数据包装成js片段。可以把数据使用js方法来包装,形成一条方法的调用语句。
3、可以使用ajax请求js片段,当js判断到达浏览器会被立即执行。
4、在浏览器端,先创建好回调方法,在回调方法中通过参数可以获得请求的数据。
原理

前期准备

1、需要把js的回调方法先写好。
2、做ajax请求时,需要把回调方法的方法名传递给服务端。
3、服务端接收回调方法名,把数据包装好,响应给客户端。

例子

首先,在客户端要把回调方法写好,简单的说就是假设你已经取到了数据,哪些方法要用到这些数据先写好。
假设一个click事件:

1
2
3
4
5
6
7
8
var url = "http://localhost:8081/data.json?callback=getDataService";
$("#button").click(function(){
$.getJSONP(url);
//或者是$.getJSONP(url,getDataService),一定要用$.getJSONP,使用$.getJSON还是会遇到无法跨域请求的问题。
});
getDateService(data){
//取得数据,执行业务逻辑
}

在服务端:
原始json数据为:

1
2
3
4
5
6
7
8
{
"data": [
{
"k": "key",
"v": "value"
}
]
}

这时候要将回调函数加到原始的json数据中,将服务端中json数据改为:

1
2
3
4
5
6
7
8
getDataService({
"data": [
{
"k": "key",
"v": "value"
}
]
}};

将原始json数据包装成js片段。此时当包装后的js片段到达浏览器时会自动执行。这里的getDataService就是浏览器端要执行的函数名。

评论