-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsearch.xml
318 lines (318 loc) · 178 KB
/
search.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
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Java8 Stream]]></title>
<url>%2F2018%2F12%2F09%2FJava8-Stream%2F</url>
<content type="text"><![CDATA[简介在Java 8中API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。在Java中只要你应用的版本是java 8以上的话都能使用该API,但是在Android中如果API低于24时,是无法使用该java API,今天我们来介绍在Android中如何使用Stream,还有常用的操作符。 什么是Stream在操作之前我们来认识一下什么是Stream? 有什么特性呢? 进入Sream API 支持顺序和并行聚合操作的一系列元素,有丰富的操作符。 除了Stream(对象引用流)之外,还有IntStream,LongStream和DoubleStream的原始特化,所有这些特性都被称为“流”,并且符合此处描述的特征和限制 Stream Pipeline 可以顺序执行或并行执行。此执行模式是流的属性。通过初始选择的顺序或并行执行来创建流。(例如Collection.stream()创建一个顺序流Collection.parallelStream() 创建一个并行流。)这种执行模式的选择可以通过BaseStream.sequential()或BaseStream.parallel()方法修改,并且可以使用BaseStream.isParallel()方法查询。 和Collections有相似之处,但是Stream不提供直接访问或操纵其元素的手段,而是涉及声明性地描述它们的源以及将在该源上聚合执行的计算操作。 支持lambda,更加方便简洁。 Android 使用Stream上文已经提及了在默认的Android API中只要在API 24以上才能使用如下:12345@RequiresApi(api = Build.VERSION_CODES.N) //必须标志在Android N才能使用,也就是API>=24才能使用public void testStream() { List<String> strings = Arrays.asList("1", "2", "2", "3", "4"); Stream.of(strings).filter(s -> !strings.isEmpty()).forEach(System.out::println);} 但是为了使用Stream的话,我们可以引用Stream依赖123456789//在Project build.gradle中添加allprojects { repositories { //... maven { url 'https://jitpack.io' } }}//在Module app build.gradle 中添加implementation 'com.annimon:stream:1.2.1' 注:这里的Stream和java 8的Stream 包名是不一样的,这是一个非官方兼容方案为一个依赖库 ,不过语法是一样的,java 8的包名是java.util.stream.Stream,而依赖库的包是com.annimon.stream.Stream,这里需要大家在使用的时候注意。 我们不能将我们Android API设置为24,那么这样是不可取的,使用选择一个依赖库也是完全可以解决需求的。 操作符使用我们先来一个简单的例子123List<String> strings = Arrays.asList("1", "2", "2", "3", "4");//输出list的元素Stream.of(strings).forEach(System.out::println); 当然了,熟悉lambda表达式的同学肯定觉得这有什么好的呢?List也直接可以输出而且很方便呢?如下:12List<String> strings = Arrays.asList("1", "2", "2", "3", "4");strings.forEach(System.out::println); 上面两个例子并没有让我们觉得Stream很牛逼是吧,不急,我们继续进行看看,Stream是不是很强大。 of返回其元素为指定值的顺序有序流。1234Stream<Integer> stream = Stream.of(1, 2, 3, 4);System.out.println(stream.count()); //stream.count():返回此流中元素的数量。输出:4 map返回一个流,该流包含将给定函数应用于此流的元素的结果123456789List<String> strings = Arrays.asList("1", "2", "2", "3", "4");//将字符串转换为int输出Stream.of(strings).map(Integer::parseInt).forEach(System.out::println);输出:12234 mapToInt返回一个IntStream,它包含将给定函数应用于此流的元素的结果。相同的还有mapToLong、mapToDouble12345678List<String> strings = Arrays.asList("1", "2", "2", "3", "4");Stream.of(strings).mapToInt(Integer::parseInt).forEach(System.out::println);输出:12234 flatMap返回一个流,该流包含将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果。123456789101112List<String> strings = Arrays.asList("1", "2", "2", "3", "4");List<String> strings1 = Arrays.asList("a", "b", "c");Stream.of(strings,strings1).flatMap(Stream::of).forEach(System.out::println);输出:12234abc filter返回由与此给定谓词匹配的此流的元素组成的流。过滤器,如下下面例子过滤空字符串12345678List<String> strings = Arrays.asList("1", "2", "2", "", "3", "4");Stream.of(strings).filter(s -> !s.isEmpty()).map(Integer::parseInt).forEach(System.out::println);输出:12234 distinct去除List重复的元素,返回由此流的不同元素(根据Object.equals(Object))组成的流。1234567List<String> strings = Arrays.asList("1", "2", "2", "", "3", "4");Stream.of(strings).filter(s -> !s.isEmpty()).distinct().map(Integer::parseInt).forEach(System.out::println);输出:1234 limit返回由此流的元素组成的流,截断为长度不超过maxSize。如下maxSize=3时。123456List<String> strings = Arrays.asList("1", "2", "2", "3", "4");Stream.of(strings).limit(3).forEach(System.out::println);输出:122 skip在丢弃流的前n个元素后,返回由此流的其余元素组成的流。如下n=3,则丢弃前面3个元素。12345List<String> strings = Arrays.asList("1", "2", "2", "3", "4");Stream.of(strings).skip(3).limit(3).forEach(System.out::println);输出:34 sorted返回由此流的元素组成的流,按照自然顺序排序。默认为升序12345678List<String> strings = Arrays.asList("1", "5", "2", "6", "4");Stream.of(strings).sorted().forEach(System.out::println);输出:12456 allMatch返回此流的所有元素是否与提供的断言匹配。boolean值12345678List<String> strings = Arrays.asList("1", "5", "2", "6", "4");System.out.println(Stream.of(strings).allMatch(s -> strings.contains("a")));输出:falseSystem.out.println(Stream.of(strings).allMatch(s->strings.contains("1")));输出:true anyMatch返回此流的任何元素是否与提供的断言匹配。1234List<String> strings = Arrays.asList("1", "5", "2", "6", "4");System.out.println(Stream.of(strings).anyMatch(s -> s.contains("1")));输出:true noneMatchStream 中没有一个元素符合传入的 断言1234List<String> strings = Arrays.asList("1", "5", "", "6", "4");System.out.println(Stream.of(strings).noneMatch(s -> s.contains("8")));输出:true collect输出为一个新的集合数据。12345678910111213141516List<String> strings = Arrays.asList("1", "5", "", "6", "4");List<String> strings1 = Stream.of(strings).filter(s -> !s.isEmpty()).collect(Collectors.toList());strings1.forEach(System.out::println);输出:1564List<String> string = Arrays.asList("1", "5", "6", "6", "4");Stream.of(string).filter(s -> !s.isEmpty()).collect(Collectors.toSet()).forEach(System.out::println);//Collectors.toSet() 不能出现重复元素,并进行排序输出。hashSet类似,但是Collectors.toSet()会进行排序输出:1456 concat创建一个延迟连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素。123456789101112131415List<String> strings = Arrays.asList("1", "5", "", "6", "4");Stream strings1 = Stream.of(strings).filter(s -> !s.isEmpty());List<String> string = Arrays.asList("8", "5", "6", "6", "4");Stream strings2 = Stream.of(string).filter(s -> !s.isEmpty());Stream.concat(strings1, strings2).forEach(System.out::println);输出:156485664 reduce使用关联累加函数对此流的元素执行减少,并返回描述减少值的Optional(如果有)。1234567int sum = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();System.out.println(sumValue1);int sum1 = Stream.of(1, 2, 3, 4).reduce(2, Integer::sum);//表示identity=2,即该sum的初始值,所以Stream中的总和等于2+(1+2+3+4)=12System.out.println(sumValue);输出:1012 sum、max、min输出Stream流的和、最大值、最小值,返回的是Optional12345678System.out.println(Stream.of(1, 2, 3, 4).reduce(Integer::sum).get());List<Integer> num = Arrays.asList(1, 3, 8, 5);System.out.println(Stream.of(num).reduce(Integer::max).get());System.out.println(Stream.of(num).reduce(Integer::min).get());输出:1081 empy返回一个空的顺序Stream。12345Stream<Integer> stream = Stream.of(1, 2, 3, 4);Stream<Integer> stream1 = stream.empty();System.out.println(stream1.count());输出:0 findFirst返回描述此流的第一个元素的Optional,如果流为空,则返回空Optional。1234Stream<Integer> stream = Stream.of(1, 2, 3, 4);System.out.println(stream.findFirst().get());输出:1 peek返回由此流的元素组成的流,另外在每个元素上执行提供的操作,因为元素是从结果流中消耗的。123456789101112Stream<String> stream = Stream.of("a", "c", "C", "f");List<String> list = stream.peek(e -> System.out.println("value: " + e)).map(String::toUpperCase).collect(Collectors.toList());list.forEach(System.out::println);输出:value: avalue: cvalue: Cvalue: fACCF toArray12345678Object[] array = Stream.of(1, 2, 3, 4).toArray();Integer[] n = Stream.of(1, 2, 4, 5, 7, 3, 11).skip(3).toArray(Integer[]::new);Stream.of(n).forEach(System.out::println);输出:57311 下载案例下载 总结Stream 常用的操作符,我们在这里的案例都差别多测试了,是不是感觉Stream给我们的感觉是一种很棒的开发案例,我们可以在开发中使用它,这样方便我们对集合的操作。 可能有些同学看到第一个的时候就觉得在哪里见过?是的,没错,是不是和RxJava的操作符的作用差不多都是非常类似的。当然了,RxJava的操作是非常丰富的,希望大家可以多多去了解。]]></content>
<categories>
<category>Stream</category>
<category>Stream操作符</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Stream</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java注解]]></title>
<url>%2F2018%2F12%2F07%2FJava%E6%B3%A8%E8%A7%A3%2F</url>
<content type="text"><![CDATA[简介由于无论在Java后台或者Android开发中我们经常遇到注解这个功能机制,例如常用的框架Java后台开发中,Spring、MyBatis等,Android的Dagger2,butterknife等,都是注解框架。今天我们就了解java是如何进行设置注解的?我们可以可以定义一个注解,方便我们使用等等。 注解元在进行了解注解时我们先来了解一下,一般注解主要包含以下几个重要的注解元,java注解的机制离不开这几个注解元。 注解元 介绍<功能> 1.@Target 注解用于什么地方,下面会介绍 2.@Retention 什么时候使用该注解 3.@Documented 注解是否将包含在JavaDoc中 4.@Inherited 是否允许子类继承该注解,表示父类如果添加此注解,子类也可以使用 5.@Repeatable java8添加的,可重复的,表该注解可以多次使用 注解的定义注解的定义很简单,就是通过@interface来进行修饰注解,就是平时我们定义接口的时候前面添加一个@符号。当然了,注解的定义需要添加一些常用的注解元,才能有效的构造我们所需的注解。如下定义注解:12public @interface TestAnnotation {} 注解元解释@Target通过@Target进行添加到注解中,说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了@target可更加明晰其修饰的目标,用于什么地方,修饰什么东西,如下常用的几种方式。 CONSTRUCTOR: 用于描述构造器 FIELD:用于描述域 LOCAL_VARIABLE:用于描述局部变量 METHOD:用于描述方法 PACKAGE:用于描述包 PARAMETER:用于描述参数 TYPE:用于描述类、接口(包括注解类型) 或enum声明如下代码123@Target({ElementType.FIELD,ElementType.METHOD}) //多个的时候使用{}括起来,然后用逗号分隔开public @interface TargetTest {} @Retention该注解是表示我们在运行或者编译时的一个保留状态,指示要保留带注释类型的注释的时间长度。 SOURCE:在源文件中有效(即源文件保留),在编译时将其抛弃掉。 CLASS:在class文件中有效(即class保留),不会添加载到JVM中 RUNTIME:在运行时有效(即运行时保留),这个和我们常用的加载是一致的,运行时会加载到JVM中,一般通过反射可获取到,一般这个是我们常用的。123@Retention(RetentionPolicy.RUNTIME)//在使用该注解其,只能选择其中一种属性,不能定义多个public @interface RetentionTest {} @Documented@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API。如下代码12345@Documented //添加文档注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface AnnotationTest {} @Inherited:@Inherited元注解是一个标记注解, @Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。123456@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Inherited //添加继承注解public @interface AnnotationTest {} @Repeatable这个是在java8添加的注解特性,@Repeatable的值表示可重复注释类型的包含注释类型。123456789@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface Repeatables { RepeatablesTest[] value();}@Repeatable(Repeatables.class)public @interface RepeatablesTest { String value() default "哈哈哈";} 例子我们简单的介绍了注解的常用的5个注解元,解下来我们通过例子来实现注解。代码如下,根据上面的提示我们进行测试。在看例子之前我们先来了解一下常用的几个方法。 Class.getAnnotations() 获取所有的注解,包括自己声明的以及继承的 Class.getAnnotation(Class< A > annotationClass) 获取指定的注解,该注解可以是自己声明的,也可以是继承的 Class.getDeclaredAnnotations()获取自己声明的注解 Class.getDeclaredField(String name); //获取该类的声明字段 Class.getDeclaredMethods();//返回的是一个Method[]数组123456@Documented@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface AnnotationTest { String name() default "小明"; //表示默认为小明。} 123456789101112@AnnotationTest() //在该类添加注解public class TestAnnotationMain { public static void main(String[] args) { boolean hasAnnotation = TestAnnotationMain.class.isAnnotationPresent(AnnotationTest.class); if (hasAnnotation) { AnnotationTest annotation = TestAnnotationMain.class.getAnnotation(AnnotationTest.class); System.out.println(annotation.name()); } }} 打印输出结果: 如果我们想更改name的值可以这么弄12345678910@AnnotationTest(name = "小刚")public class TestAnnotationMain { public static void main(String[] args) { boolean hasAnnotation = TestAnnotationMain.class.isAnnotationPresent(AnnotationTest.class); if (hasAnnotation) { AnnotationTest annotation = TestAnnotationMain.class.getAnnotation(AnnotationTest.class); System.out.println(annotation.name()); } }} 如果我们想给一个类的属性进行赋值可以这么做1.创建一个注解如下12345@Documented@Retention(RetentionPolicy.RUNTIME)public @interface AnnotationTest1 { String value(); //value来定义} 2.引用该主解1234public class Test { @AnnotationTest1(value = "小云") //引用注解进行赋值 public String name;} 3.测试1234567891011121314public class TestAnnotation1Main { public static void main(String[] args) { try { Field name = Test.class.getDeclaredField("name");//该该类的字段 name.setAccessible(true); AnnotationTest1 annotationTest1 = name.getAnnotation(AnnotationTest1.class);//获取该字段的注解 if (annotationTest1 != null) { System.out.println(annotationTest1.value()); //输出值 } } catch (NoSuchFieldException e) { e.printStackTrace(); } }} 获取方法上的注解类 如AnnotationTest2 12345@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface AnnotationTest2 { //todo无任何方法或者属性} 12345678910public class Test { @AnnotationTest1(value = "小云") public String name; @AnnotationTest2 //目的获取该AnnotationTest2 public void fun() { System.out.println("方法执行"); }} 获取12345678910111213141516171819202122public class TestAnnotation1Main { public static void main(String[] args) { try { Field name = Test.class.getDeclaredField("name"); //获取该类的声明字段 name.setAccessible(true); AnnotationTest1 annotationTest1 = name.getAnnotation(AnnotationTest1.class);//获取该字段的注解 if (annotationTest1 != null) { System.out.println(annotationTest1.value()); //输出值 } Method fun = Test.class.getDeclaredMethod("fun"); if (fun != null) { Annotation[] annotations = fun.getAnnotations(); for (int i = 0; i < annotations.length; i++) { System.out.println(annotations[i].annotationType().getSimpleName()); } } } catch (NoSuchFieldException | NoSuchMethodException e) { e.printStackTrace(); } }} 举例12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849@Documented@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation {}////////////////////////////////////////////////////////////////////////////public class MyTest { //进行获取注解方法的全部数据 @MyAnnotation public void mytestLoad() { System.out.println("测试加载"); } @MyAnnotation public void mytestRequest() { System.out.println("测试请求"); } @MyAnnotation public void mytestProgress() { System.out.println("测试进度"); } @MyAnnotation public void mytestError() { System.out.println(1 ); } ///////该方法不执行 public void mytestNoAnno(){ System.out.println("没有注解的方法"); }}////////////////////////////////////////////////////////////////////////////public class TestMain { public static void main(String[] args) { MyTest myTest = new MyTest(); Method[] methods = myTest.getClass().getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; if (method.isAnnotationPresent(MyAnnotation.class)) { try { method.setAccessible(true); method.invoke(myTest, null);//调用该类的注解方法 } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } System.out.println("==================输出完成!===================="); }} 下载Annotation案例代码下载 总结我们在开发中长见的注解如下: 常用注解 解释 @Override 方法重写 @SuppressWarnings 提示警告 @SafeVarargs 参数安全 @Deprecated 作用是对不应该再使用的方法添加注解 @FunctionalInterface 函数式编程,用于Lambda 表达式 注解给我们带来了许多方便,但是我们也得知道其优缺点。 优点注解方便我们进行单元测试,有利于进行开发。代码整洁,通过注解的方式变可知修饰的变量,或者方法。节省配置,减少配置文件大小。 缺点通过反射进行设置,可能会产生性能上的问题。若要对配置进行修改需要重新编译,扩展性差。]]></content>
<categories>
<category>Java注解</category>
<category>Java</category>
</categories>
<tags>
<tag>Java注解</tag>
<tag>Android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[EventBus源码解析]]></title>
<url>%2F2018%2F10%2F25%2FEventBus%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%2F</url>
<content type="text"><![CDATA[简介前面我学习了如何使用EventBus,还有了解了EventBus的特性,那么接下来我们一起来学习EventBus的源码,查看EventBus的源码,看看EventBus给我们带来什么惊喜以及编程思想。 这个图我从一开始就一直放置在上面了。我们在来回顾一下,EventBus的官网是怎么定义它的呢?EventBus是Android和Java的发布/订阅(观察者模式)事件总线。我们大概了解了EventBus的构建思想。接下来我们进入源码学习吧。 进入源码分析我们从EventBus的注册开始入手。1EventBus.getDefault().register(this); 1234567891011public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { //创建了EventBus实例,进入下面的方法 defaultInstance = new EventBus(); } } } return defaultInstance; } 上面的方法是一个双重校验的单例。123public EventBus() { this(DEFAULT_BUILDER); //private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder(); } DEFAULT_BUILDER是EventBusBuilder.class实例来创建的,这个类非常重要:使用自定义参数创建EventBus实例,还允许自定义默认EventBus实例,如前面的例子使用索引、配置EventBus的配置&事件的优先级&使用索引(四)等就是通过这个类来实现的,大家可以回顾一下。进入初始化一下必要的参数的构造方法EventBus(EventBusBuilder builder),如下 12345678910111213141516171819202122232425EventBus(EventBusBuilder builder) { logger = builder.getLogger(); /初始化Logger subscriptionsByEventType = new HashMap<>(); //存储订阅事件的 typesBySubscriber = new HashMap<>(); //存储相关订阅者class列表,key是注册者的对象,value是订阅者的class列表 stickyEvents = new ConcurrentHashMap<>(); //粘性事件 //下面这4个实例就是我们设置方法``threadMode = ThreadMode.xxx`` //*1 mainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null; backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); //获取注册者信息索引(添加由EventBus的注释预处理器生成的索引) indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0; //初始订阅方法查找器。这个类主要具有查找订阅的方法,继续往下看 subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); logSubscriberExceptions = builder.logSubscriberExceptions; //是否有日记订阅,默认true logNoSubscriberMessages = builder.logNoSubscriberMessages;//是否有日记信息,默认 true sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;//是否发送订阅异常事件,默认true sendNoSubscriberEvent = builder.sendNoSubscriberEvent; //是否发送没有订阅事件,默认true throwSubscriberException = builder.throwSubscriberException; //是否抛出异常处理 //默认情况下,EventBus会考虑事件类层次结构(将通知超类的注册者)。 关闭此功能将改善事件的发布。对于直接扩展Object的简单事件类,我们测量事件发布的速度提高了20%。 eventInheritance = builder.eventInheritance; executorService = builder.executorService; //创建线程池 } 我们将此段代码逐步分析.这步主要是进行初始化话一下必要的参数,如代码注解所示。下面这段代码就是我们常用的@Subscribe(threadMode = ThreadMode.xxx);初始化。一般常用的就是以下4种。1234mainThreadSupport = builder.getMainThreadSupport();mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;backgroundPoster = new BackgroundPoster(this);asyncPoster = new AsyncPoster(this); 我们来看看builder.getMainThreadSupport()方法返回的是MainThreadSupport接口,表示为支持Android主线程。上面的4个线程中都持有 PendingPostQueue等待发送的队列实例。由mainThreadSupport.createPoster(this)创建一个HandlerPoster而该类继承了Handle,并且初始化了一个等待发布队列。代码如下。123456789101112131415161718public interface MainThreadSupport { boolean isMainThread(); Poster createPoster(EventBus eventBus); class AndroidHandlerMainThreadSupport implements MainThreadSupport { private final Looper looper; public AndroidHandlerMainThreadSupport(Looper looper) { this.looper = looper; } @Override public boolean isMainThread() { return looper == Looper.myLooper(); } @Override public Poster createPoster(EventBus eventBus) { return new HandlerPoster(eventBus, looper, 10); } }} 12subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); 上面代码在这里主要初始化订阅的方法查找器。下面会讲解到它是如何进行订阅方法的。我们这这里知道了EventBus初始化,然后相关的实例的创建,接下来我我们进入到register(this)方法的调用如下方法。123456789101112public void register(Object subscriber) { //获取当前注册者对象的Class。 Class<?> subscriberClass = subscriber.getClass(); //通过该注册者的`Class`来获取当前注册者里面由`@Subscriber`注解绑定的方法。 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); //必须在线程同步中进行。 synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } } 上面的代码表示的是给注册者接收事件。 传递当前所注册的对象,如Activity、Fragment。 注意: 注册者如果不再接收信息,必须调用unregister(Object)方法,表示解除注册则该订阅者将不再接收到数据了,如果不进行解除将可能出现内存泄漏。一般在onDestroy方法解除注册后面会讲解到。 在注册者中拥有必须由@Subscribe注解的方法。@Subscribe还允许配置ThreadMode和优先级、是否是粘性行为。 接着,我们进入List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);方法。12345678910111213141516171819202122232425262728//private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); //线程安全List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //从Map集合中获取订阅方法列表 List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); //判断当前获取方法是否为空 if (subscriberMethods != null) { return subscriberMethods; } //通过EventBusBuilder.ignoreGeneratedIndex //是否忽略配置索引,默认是忽略索引 if (ignoreGeneratedIndex) { //1.通过反射获取方法列表 subscriberMethods = findUsingReflection(subscriberClass); } else { //2.通过索引方式获取订阅方法列表 subscriberMethods = findUsingInfo(subscriberClass); } //是否订阅方法为空,如果为空则表示该订阅者里面的方法都不符合EventBus订阅方法的规则 if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { //存储到集合中 METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; }} 我们逐步分析该段代码。该段代码通过注册者的类来获取当前dingy的方法列表,如果不为空则直接返回订阅方法。通过反射来查找订阅方法,如果该方法为空则抛出异常:该订阅者的方法和其超类没有@Subscriber注解的共有方法(即表示不符合EventBus订阅方法的规则)。如果不为空则存储到ConcurrentHashMap集合中。这里的是由ConcurrentHashMap集合存储是以当前订阅者的class为key,而将其由@Subscriber绑定的方法添加到List中,,就是集合的value。必须保持线程安全的,所以这里使用了ConcurrentHashMap。 1.是否忽略索引设置,该方法表示忽略设置索引,进入该方法findUsingReflection(Class<?> subscriberClass),通过反射进行获取List<SubscriberMethod> 注:设置索引在EventBus的配置&事件的优先级&使用索引(四)这里说的,大家可以去看看。123456789101112private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) { FindState findState = prepareFindState();//准备查找状态 findState.initForSubscriber(subscriberClass);// 初始化订阅者信息 while (findState.clazz != null) {//循环获取订阅者的class是否为空 //通过反射获取订阅方法, // 3.该方法下面分析 findUsingReflectionInSingleClass(findState); findState.moveToSuperclass(); //删除超类class } ////获取订阅方法list return getMethodsAndRelease(findState); } 上面的代码主要是通过反射来获取相关的订阅方法,里面由静态内部类FindState进行管理相关信息,由于上面的方法和下面索引的方法都将调用同一个方法,所以放在下面来将讲解,请看下面信息。 2.是否忽略索引设置,该方法表示的设置了索引,进入findUsingInfo(Class<?> subscriberClass)方法,如下。12345678910111213141516171819202122232425private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); //准备查找状态 findState.initForSubscriber(subscriberClass);// 初始化订阅者信息 while (findState.clazz != null) { //循环获取订阅者的class是否为空 findState.subscriberInfo = getSubscriberInfo(findState);//获取获取订阅者信息,这里如果使用添加索引的话,这里将能获取到索引的相关信息。 if (findState.subscriberInfo != null) { //获取订阅方法 SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { //进行等级检查,并存储到Map集合中,该方法checkAdd是FindState.class中的方法 ,key是@Subscriber中参数的class,value是该subscriberMethod.method,也就是@Subscribe中的方法如:public void onMessageEvent(MessageEvent event) if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { //将该订阅方法添加到列表中 findState.subscriberMethods.add(subscriberMethod); } } } else { //3.通过使用反射的方式来获取,和上面忽略索引调用到同一个方法,这里将解析 findUsingReflectionInSingleClass(findState); } //清除父类的方法 findState.moveToSuperclass(); } //获取订阅方法list return getMethodsAndRelease(findState); } 分析本段代码。首先进入查找的准备状态,通过默认状态池返回状态信息,主要存储的是FindState实例。主要存储的有订阅者的class、订阅者信息SubscriberInfo。订阅者@Subscribe方法列表List<SubscriberMethod>等.如果当前订阅者class不为空,则将获取到订阅者信息。这里分两种情况。如果使用添加索引的话,getSubscriberInfo(findState)该方法将获取到订阅信息,如果没有使用索引的话则将调用findUsingReflectionInSingleClass(findState);该方法来获取信息 使用添加索引,并返回订阅方法,该方法如下12//获取订阅方法SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); 由于AbstractSubscriberInfo类实现了SubscriberInfo接口,而SimpleSubscriberInfo继承了AbstractSubscriberInfo并实现了getSubscriberMethods方法,代码如下:本段代码在SimpleSubscriberInfo.class中。返回订阅方法数组SubscriberMethod[]。1234567891011@Override public synchronized SubscriberMethod[] getSubscriberMethods() { int length = methodInfos.length; SubscriberMethod[] methods = new SubscriberMethod[length]; for (int i = 0; i < length; i++) { SubscriberMethodInfo info = methodInfos[i]; methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode, info.priority, info.sticky); } return methods; } 3.如果没有添加索引则进入该方法findUsingReflectionInSingleClass(findState);通过反射来获取订阅者方法。这个方法有点长,慢慢看。1234567891011121314151617181920212223242526272829303132333435363738394041private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities //该方法代替getMethods()方法,获取该订阅者class全部方法。 methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 methods = findState.clazz.getMethods(); //获取该订阅者class全部方法 findState.skipSuperClasses = true; //设置跳过超类 } for (Method method : methods) { int modifiers = method.getModifiers();//获取修饰符 if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //通过与运算判断当前的修饰符,是否符合EventBus方法定义 Class<?>[] parameterTypes = method.getParameterTypes(); //获取参数类型 if (parameterTypes.length == 1) { //因为EventBus方法中必须有参数,所以当参数为1时,符合要求 //通过注解的形式@Subscribe获取Subscribe,默认的Subscribe为(priority==0,sticky=false,threadMode=POSTING),就是说ThreadMode为POSTING,粘性为false,优先级为0,如果方法中设置了相应的值,则将是你设置的值。如threadMode=MAIN。 Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation != null) { Class<?> eventType = parameterTypes[0]; //获取当前参数的class //进行等级检查,并存储到Map集合中,该方法checkAdd是FindState.class中的方法 ,key是@Subscriber中参数的class,value是该subscriberMethod.method,也就是@Subscribe中的方法如:public void onMessageEvent(MessageEvent event) if (findState.checkAdd(method, eventType)) { // 获取ThreadMode ,如MAIN ThreadMode threadMode = subscribeAnnotation.threadMode(); //将订阅方法添加到列表中 findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { //不符合EventBus订阅方法的规则要求。抛出异常,提示该方法必须是@Subscribe注解,并且需要一个参数 String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length); } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {//是一种非法的@Subscribe方法:必须是公共的,非静态的,非抽象的 String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); } } } 该段代码主要是获取该注册者的全部方法,并进行筛选出来符合EventBus的订阅方法的规则,通过注解的形式来获取订阅方法,最后添加到查找状态的列表中。首先获取的是修饰符,参数类型,再通过注解的形式来获取订阅方法。如下注解Subscribe,如果不符合EventBus相关订阅方法的规则将抛出异常提示,是否在方法上书写了错误的方法。接下来我们进入看看@Subscribe订阅方法是通过注解的形式来设置的。1234567891011@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface Subscribe { //ThreadMode ,默认POSTING ThreadMode threadMode() default ThreadMode.POSTING; //粘性事件,默认false boolean sticky() default false; //优先级,默认0 int priority() default 0;} 接下来进入getMethodsAndRelease(FindState findState)1234567891011121314private List<SubscriberMethod> getMethodsAndRelease(FindState findState) { //获取到订阅方法列表 List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods); findState.recycle(); //清空回收相关信息 synchronized (FIND_STATE_POOL) { for (int i = 0; i < POOL_SIZE; i++) { if (FIND_STATE_POOL[i] == null) { FIND_STATE_POOL[i] = findState; //将查找状态器findState,添加到状态池FIND_STATE_POOL中,为下次直接从查找状态池中获取 break; } } } return subscriberMethods; // 返回订阅方法列表 } 上面该方法通过FindState静态内部类获取了订阅的列表,然后被存储到了ConcurrentHashMap集合中。并且存储到状态池中,为方便下次读取。并且回收资源。获取到List<SubscriberMethod>订阅列表后,再次回到了注册方法中register(Object subscriber)进入到方法subscribe(subscriber, subscriberMethod);该方法通过循环进行遍历 注 此处必须是在线程同步中进行,所以添加synchronized。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556//这里的参数subscriber表示的是订阅者,如Activity等,subscriberMethod表示订阅方法,该方法就是在subscriber里面private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<?> eventType = subscriberMethod.eventType; //获取事件类型,也就是参数的class Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //通过事件类型参数class获取CopyOnWriteArrayList<Subscription>列表,该类大家可以研究一下, //是写时复制容器,即当我们往`CopyOnWriteArrayList`容器添加元素时,先从原有的数组中拷贝一份出来,然后在新的容器做写操作(添加元素),写完之后,再将原来的数组引用指向到新数组的形式存储数据的,它是线程安全的。 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); //添加到Map集合中 } else { if (subscriptions.contains(newSubscription)) { //是否已经存在了 throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { //判断优先级 subscriptions.add(i, newSubscription);//优先级高的添加添加到CopyOnWriteArrayList容器最前面,只有一个的时候就是添加在第一位了。 break; } } //获取订阅事件的class列表,即订阅方法的参数class列表 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); //存储到Map集合,key是订阅者(即上面传递的Activity对象),value是订阅事件列表 typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); //将该事件类型class添加到订阅事件列表中 判断当前方法是否是粘性的 if (subscriberMethod.sticky) { if (eventInheritance) { //必须考虑eventType的所有子类的现有粘性事件 // Existing sticky events of all subclasses of eventType have to be considered. // Note: Iterating over all events may be inefficient with lots of sticky events, // thus data structure should be changed to allow a more efficient lookup // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>). Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<?>, Object> entry : entries) { Class<?> candidateEventType = entry.getKey(); isAssignableFrom(Class<?> cls)方法表示的类型是否可以通过标识转换或通过扩展引用转换为此类对象表示的类型,则返回true if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); //将粘性事件添加到订阅者列表中 checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { //将粘性事件添加到订阅者列表中 Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } 上面的方法中主要是通过获取”事件类型”即订阅方法中的参数class。然后将其添加到Map集合中,这里用到了CopyOnWriteArrayList(原理:是写时复制容器,即当我们往CopyOnWriteArrayList容器添加元素时,先从原有的数组中拷贝一份出来,然后在新的容器做写操作(添加元素),写完之后,再将原来的数组引用指向到新数组的形式存储数据的,它是线程安全的。)然后进行判断是否设置优先级,遍历并添加到subscriptions列表中。通过当前的注册者,获取注册者class列表,并添加到typesBySubscriber集合中,key是注册者的class,如:Activity,而value就是订阅方法中的参数class的列表。这个地方说明了,一个注册者,可以拥有多个订阅事件(方法),将其绑定起来,存储到Map集合中。最后将该参数class添加到subscribedEvents.add(eventType);列表中。下面方法是检查粘性事件订阅发送事件方法,如下12345678private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { if (stickyEvent != null) { // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state) // --> Strange corner case, which we don't take care of here. //这段代码我们到post发送事件的调用的时候讲解。 postToSubscription(newSubscription, stickyEvent, isMainThread()); } } 注:这里主要是检查是否发送粘性发送事件,方法 postToSubscription我们到发送事件POST方法调用时讲解。 事件发送接下来我进入post方法,事件发送。此时,我们回忆一下简介中的图:post()==>EventBus==>将分发到各个订阅事件中。也就是订阅方法。我们来看看是如何进行操作的。代码如下 1EventBus.getDefault().post(new MessageEvent(message)); 通过以上代码我大概的知道,post()方法里面的参数是一个实体的对象,而该实体就是我们在订阅方法中的参数实体。你发现了什么了吗?前边我们已经分析了,该注册者的订阅方法主要的存储过程,接下来我们一起进入探究吧。方法跟踪进入post()方法。1234567891011121314151617181920212223242526/** Posts the given event to the event bus. */ //给订阅事件,发送事件总线 public void post(Object event) { //获取当前线程,即通过ThreadLocal本地线程来管理对应的事件线程,并且初始化发送线程状态PostingThreadState PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); //添加到事件队列中 //判断当前发送线程状态是否是正在发送的事件 if (!postingState.isPosting) { postingState.isMainThread = isMainThread(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { while (!eventQueue.isEmpty()) { //发送事件,发送完一个删除一个,直到发送全部停止循环 postSingleEvent(eventQueue.remove(0), postingState); } } finally { //设置标志改变 postingState.isPosting = false; postingState.isMainThread = false; } } } currentPostingThreadState.get()方法中我们知道123456private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); } }; 通过ThreadLocal这里就叫”本地线程”吧,来管理当前的发送线程状态,每个发送线程将得到对应一个PostingThreadState,而该PostingThreadState管理eventQueue信息,该信息主要存储的是发送事件。通过eventQueue.add(event)存储该发送事件以后完成以后,就判断当前的发送事件状态是否是正在发送中,如果还没发送则当该eventQueue不为为空的时候,进入循环发送该事件,发送完一个就删除一个,直到发送完成为止。进入方法postSingleEvent(Object event, PostingThreadState postingState)中。12345678910111213141516171819202122232425private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; if (eventInheritance) { //查找所有事件类型 List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); //发送事件 subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } if (!subscriptionFound) { if (logNoSubscriberMessages) { logger.log(Level.FINE, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } } 上面的方法首先获取该事件类型的class然后传递到lookupAllEventTypes(eventClass)方法中,通过该方法获取当前发送事件的class,包括父类、实现的接口等等列表。所以一般情况下会返回当前发送的事件,和Object.class(注:当然了,如果该事件实现接口的话,也会包括实现的接口的。)列表即List<Class<?>>,然后进行遍历进行或运算。我们先进入lookupAllEventTypes(eventClass)方法,看看返回的事件类型,并且知道他存储到Map集合中,即key是当前事件class,value是该事件的所有类型,包括父类,接口等。12345678910111213141516171819/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */ private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) { //private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>(); key是当前事件类型的class,valu则是事件的所有类型,包括父类,接口等。本事例会得到的是MessageEvent.class、和Object.class synchronized (eventTypesCache) { List<Class<?>> eventTypes = eventTypesCache.get(eventClass); if (eventTypes == null) { eventTypes = new ArrayList<>(); Class<?> clazz = eventClass; while (clazz != null) { eventTypes.add(clazz); //添加到列表中 //添加接口 addInterfaces(eventTypes, clazz.getInterfaces()); clazz = clazz.getSuperclass(); //获取父类class } eventTypesCache.put(eventClass, eventTypes); //存储d } return eventTypes; } } 看看方法postSingleEventForEventType(event, postingState, clazz),发送事件123456789101112131415161718192021222324252627282930private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { //通过当前的事件class,获取订阅事件列表 subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { //对PostingThreadState进行赋值 postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { //发送事件 postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { //设置参数为空 postingState.event = null; postingState.subscription = null; postingState.canceled = false; //设置标志 } if (aborted) { break; } } return true; } return false; } 大家是否还记得在注册的时候出现的CopyOnWriteArrayList<Subscription>在这里就用到了,通过发送事件的class获取CopyOnWriteArrayList容器里面的订阅事件信息,包括注册者对象,订阅方法等。然后遍历并进行发送事件。在此方法发布postToSubscription()事件123456789101112131415161718192021222324252627282930313233343536373839 /** * subscription:发布订阅的事件处理器 * event:当前发布的事件 * isMainThread:是否是在主线程中 */private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: invokeSubscriber(subscription, event); break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case MAIN_ORDERED: if (mainThreadPoster != null) { mainThreadPoster.enqueue(subscription, event); } else { // temporary: technically not correct as poster not decoupled from subscriber invokeSubscriber(subscription, event); } break; case BACKGROUND: if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } } 好了,这就是之前我们在注册中有一个地方遗留下的,就是判断是否是粘性状态是调用的方法,大家往回看,就看到了在方法checkPostStickyEventToSubscription中有调用到该方法。这里会根据,在方法中所选的threadMode进行调用, 1.MAIN,如果当前是否是UI线程,则会进入到invokeSubscriber(subscription, event);方法中。否则将调用mainThreadPoster.enqueue(subscription, event);这个方法将交给HandlerPoster.class里面的enqueue(Subscription subscription, Object event)方法,HandlerPoster.class现实了该接口,移交给handle完成,大家可以进入HandlerPoster.class看看,这里就不说了。 2.POSTING直接调用invokeSubscriber(subscription, event); 3.MAIN_ORDERED如果当前mainThreadPoster不为空则调用mainThreadPoster.enqueue(subscription, event);和MAIN进入HandlerPoster.class,反之调用invokeSubscriber(subscription, event); 4.BACKGROUND如果当前是主线程,则调用backgroundPoster.enqueue(subscription, event);,反之调用invokeSubscriber(subscription, event); 5.ASYNC,异步方法调用,执行asyncPoster.enqueue(subscription, event);,方法进行跟踪,该类有实现了Runnable接口,然后调用eventBus.invokeSubscriber(pendingPost);,最终将调用了invoke方法。123456789void invokeSubscriber(Subscription subscription, Object event) { try { subscription.subscriberMethod.method.invoke(subscription.subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); } } invoke()这是一个本地的方法,将调用C/C++的到方法进行发布。最后将进入到订阅方法中。并传递该事件到到订阅的方法中。好了,这里我们将post发布事件的方法简单的分析了一下。整个EventBus的分析差不多完成了,但是,我们还有一点不能忘记就是解除绑定。接下来我们来看看解除绑定的方法。 注:该方法一般生命周期的onDestroy()方法中进行解除绑定。代码如下:1EventBus.getDefault().unregister(this); 直接进入unregister(this)123456789101112/** Unregisters the given subscriber from all event classes. */ public synchronized void unregister(Object subscriber) { List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } } 1234567891011121314151617181920 /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */ /** * subscriber:指代当前解除注册的对象如Activity, * eventType:发送的事件类型,post函数的实体对象class */private void unsubscribeByEventType(Object subscriber, Class<?> eventType) { List<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions != null) { int size = subscriptions.size(); for (int i = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); if (subscription.subscriber == subscriber) { subscription.active = false; subscriptions.remove(i); //删除 i--; //目的是减少遍历次数 size--; } } }} 解除绑定,这里很简单。这里就不一一进行解释了,^_^。 总结1.本篇文章主要对EventBus源码进行简单的分析。主要是进行了观察者模式的一些高级运用。如果大家对观察者模式理解不怎么清楚可以进入这里看看简单的案例观察者模式,内容非常简单。 2.相关的EvenBut的使用,请看之前的内容。如EventBus认识(一)、EventBus的ThreadMode使用以及分析(二)等等。 3.学习本篇文章中可以认识到一些常用的类如CopyOnWriteArrayList、ThreadLocal等,到时可以深入研究一下,有助我们提高。 4.一些编程思想,设计模式值得我们去学习的,如单例模式EventBus双重校验、建造者模式,EventBus构造器的时候用到,用了初始化各个参数等等。面向接口编程而不针对实现编程。]]></content>
<categories>
<category>Android</category>
<category>源码解析</category>
</categories>
<tags>
<tag>EventBus</tag>
</tags>
</entry>
<entry>
<title><![CDATA[装饰者模式]]></title>
<url>%2F2018%2F10%2F20%2F%E8%A3%85%E9%A5%B0%E8%80%85%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[简介今天我们一起来学习装饰者模式,让我们的开发飞起来。]]></content>
<categories>
<category>设计模式</category>
<category>装饰者模式</category>
</categories>
<tags>
<tag>Java</tag>
<tag>设计模式</tag>
<tag>装饰者模式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring-Boot-单元测试(十)]]></title>
<url>%2F2018%2F10%2F17%2FSpring-Boot-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95-%E5%8D%81%2F</url>
<content type="text"></content>
</entry>
<entry>
<title><![CDATA[Spring-Boot-JPA分页和MyBatis分页实例(八)]]></title>
<url>%2F2018%2F10%2F17%2FSpring-Boot-JPA%E5%88%86%E9%A1%B5%E5%92%8CMyBatis%E5%88%86%E9%A1%B5%E5%AE%9E%E4%BE%8B-%E5%85%AB%2F</url>
<content type="text"></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
<category>MyBatis</category>
<category>JPA</category>
<category>分页</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
<tag>MyBatis</tag>
<tag>JPA</tag>
</tags>
</entry>
<entry>
<title><![CDATA[抽象工厂模式]]></title>
<url>%2F2018%2F10%2F17%2F%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"></content>
<categories>
<category>设计模式</category>
<category>抽象工厂模式</category>
</categories>
<tags>
<tag>Java</tag>
<tag>设计模式</tag>
<tag>抽象工厂模式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring Boot 定时器(九)]]></title>
<url>%2F2018%2F10%2F03%2FSpring-Boot-%E5%AE%9A%E6%97%B6%E5%99%A8-%E4%B9%9D%2F</url>
<content type="text"><![CDATA[简介在项目中我们有时需要做定时任务,那么我们在Spring Boot项目中如何添加定时任务呢?非常简单,让我们一起学习吧。 配置主要是添加注解的方式进行1、在启动类Application.class中添加注解 @MapperScan(“com.example.myjpa.dao”)//mybatis的注解 @EnableScheduling//定时器注解 12345678@SpringBootApplication@EnableSchedulingpublic class MyApplication { public static void main(String[] args) { SpringApplication.run(MyjpaApplication.class, args); }} 2、创建ScheduledTasks.class做定时任务,添加注解 @Configuration //1.主要用于标记配置类,兼备Component的效果。 @EnableScheduling // 2.开启定时任务 123456789101112@Configuration //1.主要用于标记配置类,兼备Component的效果。@EnableScheduling // 2.开启定时任务public class ScheduledTasks { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); //表示定时器时间定义方式 //@Scheduled(cron = "0/5 * * * * ?") //@Scheduled(fixedRate = 1000 * 60) @Scheduled(cron = "0 0/2 * * * ?") public void configureTasks() { System.out.println("The time is now " + dateFormat.format(new Date())); }} 3、创建config定时器配置CompleteScheduleConfig.class,添加注解 @Configuration@EnableScheduling 1234567891011121314151617181920212223242526@Configuration@EnableSchedulingpublic class CompleteScheduleConfig implements SchedulingConfigurer { @Autowired StudentRepository studentRepository; private static String DEFAULT_CRON = "0 0/2 * * * ?";//表示间隔2分钟 @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {// scheduledTaskRegistrar.scheduleFixedDelayTask(new FixedDelayTask(() -> {//// }, 10000, 1000 * 60)); scheduledTaskRegistrar.addTriggerTask(() -> { Student student = new Student(); student.setName("ok"); student.setAge(12); student.setSex("nan"); studentRepository.save(student); System.out.println("===插入成功!==="); }, triggerContext -> { return new CronTrigger(DEFAULT_CRON).nextExecutionTime(triggerContext); }); }} 注:这个案例主要是通过定时利用JPA操作数据的方式进行代码编写案例(案例是进行每隔2分钟就插入一条数据),在开发项目中假如数据刷新我们可以用定时任务完成、股票类数据刷新等等。所以定时任务在后台任务中也是一个重点。 cron表达式大家可以进入这里测试cron在线表达式生成工具本篇案例中"0 0/2 * * * ?"是表示每个2分钟运行一次。 认识cron表达式 名词 范围 表达式 秒 0-59 - * / 分 0-59 - * / 时 0-23 - * / 日期 1-31 - * ? / L W C 星期 1-7或者 SUN-SAT - * ? / L C # 年(非必填) 1970-2099 - * / 字符解析 表达式字符 注释 ? 表示所有值、不确定的值 , 表示附加数个可能的值 - 表示指定值的范围 / 表示指定从哪个值开始,然后按照哪个值进行递增,如i/j表示从i开始,每次按照j值进行递增 L,late简写 表示在日时是这个最后一天,在周期时是表示这个月最后一个星期几 W weekday简写 表示离给定日期最近的工作日(周一到周五) # 表示这个月第几个周几,如:6#3表示这个月第3个周五(6表示周五,3表示的是第3个) 举例 表达式 注解 "0/1000 * * * * ?" 每1000秒执行一次 */1000 * * * * ? 每隔1000秒执行一次 "0 0 10 * * ?" 每天10点整执行一次 0 0 5-15 * * ? 每天5-15点整点执行一次 0 0/30 9-18 * * ? 早上9点到下午18点工作时间内每半小时执行一次 0 0 12 ? * WED 每个星期三中午12点执行 0 0 18 ? * TUES,THUR,SAT 每周二、四、六下午6点执行一次 "20,30,40 * * * * ?" 每20秒,30秒,40秒时执行一次 "0 15 10 L * ?" 表示每个月最后一天的10点15分0秒执行一次 "0 15 10 LW * ?" 表示每个月最后一个工作日的10点15分0秒执行一次 下载 本篇案例代码下载-码云 本篇案例代码下载-GitHub Spring Boot系列代码-码云 Spring Boot系列代码-GitHub 总结本篇案例主要讲解的是在Spring Boot运用定时器,这是在我的项目中用到了,所有总结为一个模块进行案例分析,代码很简单。这里就不带大家进行单元测试了。 推荐 我的博客https://eirunye.github.io进行浏览相关技术文章,大家一起相互探讨技术。 如果大家想了解更多的Spring Boot相关博文请进入我的Spring Boot系列博客栈]]></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
<category>定时器</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
<tag>定时器</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring Boot MyBatis集成以及操作数据库(七)]]></title>
<url>%2F2018%2F10%2F02%2FSpring-Boot-MyBatis%E9%9B%86%E6%88%90%E4%BB%A5%E5%8F%8A%E6%93%8D%E4%BD%9C%E6%95%B0%E6%8D%AE%E5%BA%93-%E4%B8%83%2F</url>
<content type="text"><![CDATA[简介我们都知道以前搭建项目的时候都是SSM(Spring+SpringMVC+MyBatis)的方式来进行的。那么我们Spring Boot项目是否也能集成MyBatis呢?进而操作数据库数据呢?答案是肯定的。本篇博文主要讲解的是Spring Boot开发项目中如何集成MyBatis,MyBatis是如何用来操作数据库?带着这样的疑问我们一起来学习吧。 认识MyBatisMyBatis是什么?在这之前我们可能什么都不知道,我们只知道它能快速帮我们构造操作数据库数据工具。那我们是怎么才能了解呢?非常简单,浏览相关技术文章快速便捷。大家在这里一定要养成好的习惯,在开发中遇到不懂的技术难题的时候,进行搜索,这个谁都会,但是如果有时间一定要进入其官网查看人家的官网,毕竟官网都是有浅入神,原理理解起来简单,但是要求其英语要好点,不然有点吃力。所以还是建议大家多读英语,多看官方文档。MyBatis官方文档在这里。 1.什么是 MyBatis ? (原)MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of theJDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Mapinterfaces and Java POJOs (Plain Old Java Objects) to database records. (译)MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。 好了,我们大概了解了一下MyBatis,那我们就从最简单的开始吧。 配置我们需要在我们的Spring Boot项目集成MyBatis,所以现在我们就开始进行集成MyBatis到我们项目中吧。 在Maven创建的项目进行配置1.打开pom.xml添加依赖1234<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId></dependency> 在Gradle创建的项目进行配置添加依赖12 或者jar包引入在这里下载jar包,然后添加在Lib目录下然后添加配置 说明由于我们经常需要到封装Json数据访问输出,所以这里我创建了一个comment-module依赖包,每次每个公共类或者是一些Utils包,封装在其里面,这样每个项目都可以进行引用了。不在module如创建情况这里:创建module方式 代码解析1.创建文件 2.创建mapper 3.添加注解 测试1.单元测试 注:这里不进行Posman请求测试了,大家到时自行测试,等我们讲解该篇完成后会进行单元测试重点分析。 总结1.本篇文章我们学习了Spring Boot项目如何集成MyBatis,并且进行了了数据库的基本操作。 2.相关的配置文件还有操作的xml文件,创建步骤,大家要记住。 3.接下来我们将深入分析MyBatis在项目中常用的功能如:分页查询等操作。 4.后期的文章将会深入解析MyBatis高级使用、利用IDEA重定向自动生成xml配置文件等等。 推荐 我的博客https://eirunye.github.io进行浏览相关技术文章,大家一起相互探讨技术。 如果大家想了解更多的Spring Boot相关博文请进入我的Spring Boot系列博客栈]]></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
<category>MyBatis</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
<tag>MyBatis</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring Boot JPA操作数据(六)]]></title>
<url>%2F2018%2F09%2F29%2FSpring-Boot-JPA%E6%93%8D%E4%BD%9C%E6%95%B0%E6%8D%AE-%E5%85%AD%2F</url>
<content type="text"><![CDATA[简介在Spring Boot项目开发中,我们操作数据库的时候一般会用到JPA或者是MyBatis框架,今天所要将的是JPA操作数据库案例。后续会说到MyBatis操作数据。大家在开发的时候,看看自己对哪个比较熟练或者是公司项目的需要来选择,上篇我们已经说了Spring Boot如何连接数据库,我们紧跟着上篇Spring Boot 连接数据库(五)的内容来进行操作。 认识JPA首先我们在遇到一个新的知识的时候,官方技术文档将是我们最好的帮手,或者是网上查询相关技术支持博文。进入JPA官方文档学习吧。 什么是JPA? Spring Data JPA, part of the larger Spring Data family , makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. 翻译如下: Spring Data JPA是更大的Spring Data系列的一部分,可以轻松实现基于JPA的存储库。 此模块处理对基于JPA的数据访问层的增强支持。 它使构建使用数据访问技术的Spring驱动应用程序变得更加容易。Spring Data JPA旨在通过减少实际需要的工作量来显着改善数据访问层的实现 JPA有什么特性? 基于Spring和JPA构建存储库的复杂支持 支持Querydsl术语,从而支持类型安全的JPA查询 透明审核域名类 分页支持,动态查询执行,集成自定义数据访问代码的能力 在引用时可通过@Query带注释的自定义查询 支持基于XML的实体映射 基于JavaConfig的存储库配置,介绍@EnableJpaRepositories 配置JPA要求 JAVA 8或者更高 Gradle 4+ or Maven 3.2+ 在Spring Boot中通过JPA来进行操作数据库是非常简便的,并且我们也可以自定义SQL语句,而在满足我们项目的条件下不需要进行书写过多的SQL语句,这能很大程度解决我们的开发效率。 配置 注:本案例选择的开发工具IntelliJ IDEA Spring Boot项目是如何进行配置JPA呢?这里我用的是Mevan和Gradle两种方式进行项目的配置来创建Spring Boot项目,但是在本案例开发提供代码是通过Mavan进行的,后期会补充Gradle的案例代码。 Maven配置1.添加JPA maven依赖库 在pom.xml添加 1234567891011121314151617181920212223<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--mysql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <!--JPA依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> 注:这里因为要操作数据,所以需要添加数据库依赖,如MySQL 2.在application.yml配置文件中进行配置(推荐)12345678910111213141516171819spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # MySql jdbc Driver # 连接数据库 # eirunye_springboot_notes表示的是你创建的数据库; # useSSL:是否使用SSL证书验证; # characterEncoding:编码格式; # useJDBCCompliantTimezoneShift:是否使用符合JDBC的时区转换; # useLegacyDatetimeCode:是否使用旧版日期时间码; # serverTimezone:选择服务器时间方式; url: jdbc:mysql://127.0.0.1:3306/eirunye_springboot_notes?useSSL=false&requireSSL=false&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: root #本地设置数据库账号 password: 123456 #密码 jpa: hibernate: ddl-auto: update show-sql: trueserver: port: 8090 #访问端口 注 spring.jpa.hibernate.ddl-auto配置的属性解释: create:每次启动该程序,没有表的会新建表,表内有数据都会清空,结束时,并未清空表 create-drop:每次启动、结束程序的时都会清空表,而且启动时会重新创建表, update:每次启动程序时,没有表的会创建表,表不会清空,检查表字段是否相同,不同则更新, validate:每次启动程序时,校验数据与表(字段、类型等)是否相同,不同会报错 none:在所有其他情况下,默认为none,这里选择update 在application.properties配置文件中进行配置 注:.yml和.properties选择其一 1234567server.port=8090spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # MySql jdbc Driverspring.datasource.url=jdbc:mysql://127.0.0.1:3306/eirunye_springboot_notes?useSSL=false&requireSSL=false&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTCspring.datasource.data-username=rootspring.datasource.data-password=123456spring.jpa.hibernate.ddl-auto=updatespring.jpa.show-sql=true Gradle配置1.在build.gradle添加依赖1234567dependencies { //注:Android 的Gradle依赖已经改为implementation compile("org.springframework.boot:spring-boot-starter-data-jpa") //添加jpa依赖 compile ("mysql:mysql-connector-java:5.1.24") //添加MySQL依赖 compile("com.h2database:h2") testCompile("junit:junit")} 2.在.yml或者.properties配置文件中进行配置和上面的Maven一样。 JPA操作数据 注:在这里简单的举例说明JPA的使用,在系列文章会更加深入讲关于JPA的使用,在这里的代码创建到是按照Spring Boot 项目如何搭建(四)项目包层级定义来创建的,所以大家不清楚就再去看看,或者下载本例代码查看就明白了。 1.在.bean包下创建实体User.class 注:这里添加注解的位置如果选择在属性上面添加的话就全部都选择在属性上面添加,本例的User.class,如果选择在get方法上面添加,那么全部都在get方法上面添加注解,不然有时可能获取不到数据,或者其他错误。 12345678910111213141516171819202122232425@Entity@Table(name = "user")public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(name = "username",columnDefinition = "VARCHAR(50) NOT NULL COMMENT '用户名不为空!'",unique = true) @NotBlank(message = "username can't be null") private String username; @Column(name = "password",columnDefinition = "VARCHAR(50) NOT NULL COMMENT '密码不为空!'") @NotBlank(message = "password can't be null") private String password; @Temporal(TemporalType.DATE) @Column(name = "create_time", columnDefinition = "datetime DEFAULT NULL COMMENT '创建时间'") private Date createTime; //创建时间 @Temporal(TemporalType.DATE) @Column(name = "update_time", columnDefinition = "datetime DEFAULT NULL COMMENT '更新时间'") private Date updateTime;//更新时间// get/set方法 注:Lombok插件可以直接构建get、set方法} 实体注解解释(这里讲解的是常用的) 注解源 解释说明 @Entity JPA实体映射到数据库 @Table 表示表,@Table(name = "user")表示该实体生成的表是user@Table还有几个属性,如catalog:对应数据库的catalog,schema:对应数据库的schema,UniqueConstraint定义在@Table或@SecondaryTable元数据里,用来指定建表时需要建唯一约束的列等 @Id 表示主键Id @GeneratedValue @GeneratedValue(strategy = GenerationType.AUTO)表示Id自增 @Column name = "username"表示表的字段名为username,columnDefinition表示字段创建的SQL语句说明,第一次创建时生效,unique=true表示该字段的值在表中是唯一的(如:用户名不能相同) @NotBlank @NotBlank(message = "password can't be null")解释说明 @Temporal @Temporal(TemporalType.DATE)表示时间的定义格式为:yyyy-MM-dd 即:2018-09-01,TemporalType.TIME表示为:hh:mm:ss 即 10:12:11,TemporalType.TIMESTRAMP表示为:yyyy-mm-dd hh:mm:ss 即:2018-09-01 10:12:11 说明:以上的实体注解解释完成了。当然了,在这里我们使用的JPA的注解只是很少的一部分,接下来的文章会详细说明相关注解的作用和使用。 运行项目生成的表如下1234567891011create table user( id int not null primary key, create_time datetime null comment '创建时间', password varchar(50) not null comment '密码不为空!', update_time datetime null comment '更新时间', username varchar(50) not null comment '用户名不为空!', constraint UK_sb8bbouer5wak8vyiiy4pf2bx unique (username)) engine = MyISAM charset = utf8; 2.在.controller包下创建UserController.class这里代码的就是测试增删改查,这里的注解之前的文章都解释过了,这里就不解释了,遇到最新的就解释。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657@RestController@RequestMapping("/user")public class UserController { @Autowired UserService userService; /** * 通过id查找user信息 * @param id 参数id * @return 返回Json user * @throws Exception */ @GetMapping(value = "/info/{id}") public Result<User> getUserDataById(@PathVariable Integer id) throws Exception { if (null == id) throw new EirunyeException(ResultEnum.UNKNOWN_ERROR);//这里定义自己的提示错误信息,最好每个都有定义这样比较明确错误!!! return userService.getUserDataById(id); } /** * 插入数据 * @param user 用户信息 * @return * @throws Exception */ @PostMapping(value = "/info/save") public Result<User> saveUserData(@Valid User user) throws Exception { if (null == user) throw new EirunyeException(ResultEnum.UNKNOWN_ERROR);//这里定义自己的提示错误信息,最好每个都有定义这样比较明确错误!!! return userService.saveUserData(user); } /** * 更新user信息 * @param user 参数 * @return 返回提示 更新失败或者成功 * @throws Exception 异常 */ @PostMapping(value = "/info/update") public Result<String> updateUserInfo(@Valid User user) throws Exception { if (null == user) throw new EirunyeException(ResultEnum.UNKNOWN_ERROR); return userService.updateUserInfo(user); } /** * 通过ID参数user信息 * @param id 参数id * @return 返回提示 参数成功或者失败 */ @GetMapping(value = "info/delete/{id}") public Result<String> deleteUserInfo(@PathVariable Integer id) throws Exception { if (null == id) throw new EirunyeException(ResultEnum.UNKNOWN_ERROR); return userService.deleteUserInfo(id); }} 3.在.service包下创建UserService.class 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475@Servicepublic class UserService { @Autowired UserRepository userRepository; /** * 插入数据 * @param user 用户 * @return Json user * @throws Exception 异常抛出 */ public Result<User> saveUserData(User user) throws Exception { User user1 = userRepository.save(user); return ResultUtil.getResult(ResultEnum.SUCCESS, user1); } /** * 根据id来获取user信息 * @param id 参数id * @return Json user */ public Result<User> getUserDataById(Integer id) throws Exception { User user = userRepository.findAllById(id); if (null == user) { throw new EirunyeException(ResultEnum.USERNOTEXIST);//用户不存在 } else return ResultUtil.getResult(ResultEnum.SUCCESS, user); } /** * 更新user信息 更新数据说明: * 1.如果通过save(S s)方法的话必须带主键Id,这样通过主键id就能更新数据了, * 如果不带参数id则,数据将会自增一条数据(变成插入数据了) * 2.自定义SQL语句更新数据(后面会讲到) * * @param user 参数user * @return json String */ public Result<String> updateUserInfo(User user) throws Exception { if (null == user.getId()) return ResultUtil.error(-1, "Id不能为空!"); //更新数据 User userUpdate = userRepository.save(user); if (userUpdate == null) return ResultUtil.error(-1, "数据更新失败,请联系后台!"); // 一般更新数据步骤: // 1.通过ID获取当前用户信息 // 2.将所需更新的信息进行设置,如用户名、密码等,而创建时间不需要更新 // 3.如果没有其他特殊字段直接save方法更新(注:这里我就简单直接save了,大家的项目应该是下面这样方式写) //User userInfo = userRepository.findAllById(user.getId()); //userInfo.setId(user.getId()); //userInfo.setUsername(user.getUsername()); //userInfo.setPassword(user.getPassword()); //userInfo.setUpdateTime(new Date()); //userInfo.setCreateTime(new Date()); //这个不需要设置 return ResultUtil.getResult(ResultEnum.SUCCESS, "更新数据成功!!!"); } /** * @param id 参数id * @return json String */ public Result<String> deleteUserInfo(Integer id) throws Exception { userRepository.deleteById(id); //下面不需要也可以,直接返回return ResultUtil.getResult(ResultEnum.SUCCESS, "删除数据成功!!!"); User user = userRepository.findAllById(id); if (null == user) { throw new EirunyeException(ResultEnum.SUCCESS); } else return ResultUtil.error(-1, "删除数据失败!!!"); }} 4.在.repository创建UserRepository接口去扩展JpaRepository<T,ID> 注:JpaRepository<T,ID>一般T是要操作的实体,如本例的User.class,ID是该实体ID类型,如本例定义id为Integer,如果你自己定义Long这里就表示Long。在UserRepository接口这里需要注意的是命名规则查找find,删除delete等等 1234567891011121314151617181920212223public interface UserRepository extends JpaRepository<User, Integer> { /** * @param id 根据id获取当前user数据 * @return user */ User findAllById(Integer id); /** * 通过用户名和密码来获取用户 * @param username 参数用户名 * @param password 参数密码 * @return 返回user */ User findAllByUsernameAndPassword(String username, String password); /** * 模糊查询 自定义查询语句 * @param username * @return */ @Query(value = "select * from user u where u.username like %:username%",nativeQuery=true) List<User> findAllByUsername(@Param("username") String username);} 注: JPA的自定义语句都是@Query(value="SQL语句"),下面的方法还是要按照JpaRepository命名规则进行,后面的文章将会说明该内容。 测试新建单元测试在src\test\java\com\eirunye\spring_boot_jpa\创建UserTest.class,这里只是简单的进行单元测试,之后文章会讲解到Spring Boot项目如何进行单元测试。测试代码UserTest.class进行查看,这里只贴一些 123456789101112131415161718192021222324252627282930@RunWith(SpringRunner.class)@SpringBootTestpublic class UserTest { private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class); @Autowired UserService userService; @Test public void getUserInfo() throws Exception { Result<User> userDataById = userService.getUserDataById(1); logger.info(userDataById.getData().toString()); System.out.println(userDataById.getData().toString()); } @Before //@Before是在这个测试类中点击每个@Test都会执行的注解,在`@Test`之前执行 public void saveUserInfo() throws Exception { User user = new User(); user.setUsername("Eirunye"); user.setPassword("123456"); user.setCreateTime(new Date());//这里只做测试用,一般都是后台处理时间 user.setUpdateTime(new Date());//这里只做测试用,一般都是后台处理时间 System.out.println(user.toString()); Result<User> userResult = userService.saveUserData(user); System.out.println(userResult); }//......} 测试验证 测试id输入错误时,如:userService.getUserDataById(0); 查看数据库 IDEA请求数据测试这里只列举一个,大家进测试就好了。 Postman请求数据测试这里只列举一个,大家进测试就好了。 下载 本篇案例代码下载-码云 本篇案例代码下载-GitHub Spring Boot系列代码-码云 Spring Boot系列代码-GitHub 总结1.本篇文章主要讲解的是JPA与Spring Boot整合操作数据,只是简单的说明JPA的增删改查功能,接下来的文章会深入研究JPA的更多内容,如:JPA的多表操作如何使用、JPA分页操作等等。 2.JPA在项目中使用能使我们快速构建项目,不需要书写过多的SQL语句(相信这是大家都非常喜欢的) 3.前文开始的时候说了JPA一些特性。 4.Android平台上的GreenDao的使用方式和JPA是非常相似的 推荐如果大家想了解更多的Spring Boot相关博文请进入我的Spring Boot系列博客栈]]></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
<category>JPA</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
<tag>JPA</tag>
</tags>
</entry>
<entry>
<title><![CDATA[简单工厂模式]]></title>
<url>%2F2018%2F09%2F26%2F%E7%AE%80%E5%8D%95%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[简介1.学习本篇文章,了解简单工厂设计模式的使用场景。2.如何使用简单工厂模式。3.简单工厂模式能解决什么问题?回到优缺点。 场景现在有一家外卖小店需要从生产一份外卖开始进行考虑设计,当客户在网上点出不同味道的菜时,外卖小店就将按照不同的订单进行生产出菜品,然后进行打包、等待外卖小哥获取、赠送给客户等不同的几道工序,才算完成一单,但是后期由于生意很好,客户评价很高,有些地方的老板想加盟本店了,那么可能就会出现不同味道的菜单,还有一些本地的特色菜也会加上菜单,这时,客户就能点到更多的菜品了,为了达到某些商家快速而且合理的进行管理,你是怎么设计这个方案的呢? 分析一般情况下客户点餐的时候,都会查看平分高低,还有出货次数,如果人气好的,肯定要大量生产,而没有人点的,则可以考虑去除,所以我们通过以上的考虑,需要封装创建对象的代码,如将生产商品的对象封装起来,这样,我们出掉或者添加的时候,都只修改这部分的代码,那么是哪个生产商品呢?答案肯定是店家了,所以我们称这个生产商品的店家为“工厂”。接下来我们进行分析以上代码该如何展现出来呢?我们将通过两种方式来进行编写。 静态工厂模式如何实现呢?我们先来看看本例的类图 1.创建一个美食店,MealStore.class 1234567891011121314151617181920212223242526272829/** * Author Eirunye * Created by on 2018/9/19. * Describe */public class MealStore { public Meal submitOrderMeal(String type) { Meal meal; //利用静态工厂方法生成产品 //重点(静态工厂实现代码) meal = SimpleMealsFactory.createMeal(type); //以下方法是进行一些设计而已不是静态工厂方法的模块,是产品的一些操作而已,无关紧要的 //店家准备中... meal.mealPreparation(); //打包 meal.bake(); //获取 meal.getMeal(); //配送 meal.send(); return meal; }} 2.创建一个静态工厂SimpleMealsFactory.class 123456789101112131415161718192021222324252627/** * Author Eirunye * Created by on 2018/9/18. * Describe 建立一个简单静态工厂,该工厂生产不同的菜品(美食) * Tip: 提示:这是不是我们经常使用的Util类的编写方式? */public class SimpleMealsFactory { //将生产产品(美食放在这里) public static Meal createMeal(String type) { Meal meal = null; if ("crayfish".equals(type)) { meal = new CrayfishMeal(); //创建什么类型的产品(美食)让子类来操作 } else if ("roastChicken".equals(type)) { meal = new RoastChicken(); } else if ("A".equals(type)) { } else if ("B".equals(type)) { } // ... return meal; //返回的是美食商品 }} 3.创建产品美食抽象基类Meal.class,这样的话我们就可以交给子类来完成商品的生产。 12345678910111213141516171819202122232425262728/** * Author Eirunye * Created by on 2018/9/19. * Describe 定义一个抽象产品接口、这里也可以是抽象类 */public abstract class Meal {public List mealInfo = new ArrayList(); public void mealPreparation() { System.out.println("您的商品准备中..."+this.name); System.out.println("adding material..."); System.out.println("adding condiment..."); System.out.println("adding mealInfo..."); for (int i = 0, len = mealInfo.size(); i < len; i++) { System.out.println(" [" + mealInfo.get(i) + "]"); } } public void bake() { System.out.println("进行打包,只需1分钟就能打包完成!"); } public void getMeal() { System.out.println("外卖小哥获取美食,外卖小哥可能需要花费1~20分钟来获取美食!"); } public void send() { System.out.println("配送给客户,配送需要大概5~40分钟送达!"); }} 4.这里模拟具体产品,必须去实现Meal接口或者去派生该抽象类,如下小龙虾美食:CrayfishMeal.class。 12345678910111213/** * Author Eirunye * Created by on 2018/9/19. * Describe 模拟产品(具体的美食--小龙虾) */public class CrayfishMeal extends Meal { public CrayfishMeal() { setName("香辣小龙虾....."); setMaterial("添加5份小龙虾..."); setCondiment("添加适量的调味品..."); mealInfo.add("生成可口的小龙虾..."); }} 5.测试 1234567891011121314/** * Author Eirunye * Created by on 2018/9/19. * Describe */public class Test { public static void main(String[] args) { //创建一个工厂类(即商品总店) MealStore mealStore = new MealStore(); //这里我们要传递在静态工厂实例定义的字符串,否则将报空指针。 mealStore.submitOrderMeal("roastChicken"); }} 输出结果: 123456789101112"C:\Program Files\Java\jdk1.8.0_161\bin\java"...您的商品准备中...烧鸡.....adding material...adding condiment...adding mealInfo... [生成可口的烧鸡...]进行打包,只需1分钟就能打包完成!外卖小哥获取美食,外卖小哥可能需要花费1~20分钟来获取美食!配送给客户,配送需要大概5~40分钟送达!Process finished with exit code 0 注:首先我们将生产商品创建放在了静态工厂中,静态工厂处理对象创建的细节,MealStore 美食店只关心如何得到美食商品就可以了,并进行打包、获取、派送等操作,而这样操作起来方便了产品的创建了。 简单工厂方法我们通过静态工厂方法的方式实现了该功能,但是,是否有更好的封装方式呢?接下来我们来分析一下吧。本例类图关系 1.现在我们将MealStore.class,修改为抽象类,并且将生产商品的方法也修改为抽象方法,我们这样做的目的,为什么呢?我们这样做是让各个分店子类来实现商品的生产,扩展性更高,封装性更加完善,而该抽象类并不知道是哪个子类来完成商品的创建,达到了耦合。代码如下: 1234567891011121314151617181920212223242526272829303132333435/** * Author Eirunye * Created by on 2018/9/18. * Describe 抽象工厂基类 美食总店 */public abstract class MealStore { /** * 客户下单 * * @param type 选择什么样的美食 * @return 美食 Meal */ public Meal submitOrderMeal(String type) { Meal meal; //这里是我们将生产的美食 meal = createMeal(type); //店家准备中... meal.mealPreparation(); //打包 meal.bake(); //获取 meal.getMeal(); //配送 meal.send(); return meal; } //实现抽象的工厂方法,让每个分店来完成此生产操作, protected abstract Meal createMeal(String type);} 2.创建分店子类JiangNanMealStore .class派生自MealStore.class,进行生产商品 1234567891011121314151617181920/** * Author Eirunye * Created by on 2018/9/18. * Describe JiangNanMealStore分店,这里是需要增加修改的地方,可能有新的菜品的时候就在这里增加或者删除*** */public class JiangNanMealStore extends MealStore { @Override protected Meal createMeal(String type) { return getMeal(type); } //让子类来创建产品 private Meal getMeal(String type) { if ("crayfish".equals(type)) { return new CrayfishMeal();//小龙虾 } else if ("roastDuck".equals(type)) { return new RoastDuckMeal(); } else return null; }} 3.创建商品抽象基类Meal.class 12345678910111213141516171819202122232425262728/** * Author Eirunye * Created by on 2018/9/18. * Describe 抽象产品父类 美食抽象类 */public abstract class Meal {public List mealInfo = new ArrayList(); public void mealPreparation() { System.out.println("您的商品准备中..."+this.name); System.out.println("adding material..."); System.out.println("adding condiment..."); System.out.println("adding mealInfo..."); for (int i = 0, len = mealInfo.size(); i < len; i++) { System.out.println(" [" + mealInfo.get(i) + "]"); } } public void bake() { System.out.println("进行打包,只需1分钟就能打包完成!"); } public void getMeal() { System.out.println("外卖小哥获取美食,外卖小哥可能需要花费1~20分钟来获取美食!"); } public void send() { System.out.println("配送给客户,配送需要大概5~40分钟送达!"); }} 3.定义具体子类CrayfishMeal.class产品,扩展自Meal .class 12345678910111213141516171819/** * Author Eirunye * Created by on 2018/9/18. * Describe 具体产品 小龙虾美食 */public class CrayfishMeal extends Meal { public CrayfishMeal() { setName("可口的小龙虾....."); setMaterial("添加几份小龙虾+一些材料..."); setCondiment("添加适量的调味品..."); mealInfo.add("生成可口的小龙虾..."); } //这里重写了父类的方法 @Override public void bake() { System.out.println("小龙虾分成5份打包!"); }} 4.测试 12345678910111213141516171819/** * Author Eirunye * Created by on 2018/9/18. * Describe */public class Test { public static void main(String[] args) { MealStore jiangNanMealStore = new JiangNanMealStore(); jiangNanMealStore.submitOrderMeal("crayfish"); System.out.println("江南店完成一单"); System.out.println("==============================\n"); MealStore guangDongMealStore = new GuangDongMealStore(); guangDongMealStore.submitOrderMeal("roastDuck"); System.out.println("广东店完成一单"); }} 输出结果 1234567891011121314151617181920212223"C:\Program Files\Java\jdk1.8.0_161\bin\java"...您的商品准备中...可口的小龙虾.....adding material...adding condiment...adding mealInfo... [生成可口的小龙虾...]小龙虾分成5粉打包!外卖小哥获取美食,外卖小哥可能需要花费1~20分钟来获取美食!配送给客户,配送需要大概5~40分钟送达!江南店完成一单==============================您的商品准备中...江南烤鸭.....adding material...adding condiment...adding mealInfo... [生成可口的烤鸭...]进行打包,只需1分钟就能打包完成!外卖小哥获取美食,外卖小哥可能需要花费1~20分钟来获取美食!配送给客户,配送需要大概5~40分钟送达!广东店完成一单Process finished with exit code 0 下载本篇案例代码–码云 本篇案例代码–GitHub 设计模式系列–码云 设计模式系列–GitHub 总结我们通过简单的例子了解了简单工厂模式的开发案例。其实,简单工厂并不是我们常说的23中设计模式,他只是我们常用的一种编程习惯罢了,抽象工厂,才是我们常用的设计模式,接下来我们会讲解到抽象工厂模式。 静态工厂(简单工厂)和简单工厂方法有什么不同呢? 静态工厂将生产商品都定义在静态工厂的方法内,而简单工厂方法是交给子类来完成。但是静态工厂(简单工厂)不具备简单工厂方法所具有的扩展性强。简单工厂方法的子类将会出现大量相同的代码,但是同时它也可以重写分类的方法,完成自己定义操作。 在Android中也常用到静态工厂或者是工厂方法等编程设计思路,如AnimationUtils类获取的各个子类的动画,BitmapFactory等 推荐大家可以到我的博客https://eirunye.github.io进行浏览相关文章,大家一起相互探讨技术。 设计模式系列大家可以了解相关文章。]]></content>
<categories>
<category>设计模式</category>
<category>简单工厂方法</category>
<category>静态工厂</category>
</categories>
<tags>
<tag>Java</tag>
<tag>设计模式</tag>
<tag>简单工厂方法</tag>
<tag>静态工厂</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hexo搭建GitHub博客—打造炫酷的NexT主题--高级(五)]]></title>
<url>%2F2018%2F09%2F23%2FHexo%E6%90%AD%E5%BB%BAGitHub%E5%8D%9A%E5%AE%A2%E2%80%94%E6%89%93%E9%80%A0%E7%82%AB%E9%85%B7%E7%9A%84NexT%E4%B8%BB%E9%A2%98%E2%80%94%E9%AB%98%E7%BA%A7%E2%80%94%E4%BA%94%2F</url>
<content type="text"><![CDATA[简介上篇Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(四)讲解到Hexo NexT的相关配置如:设置字体、背景动画、添加打赏等等。大家还没设置可以去查看根据自己的需要。接下来继续对NexT主题博客进行配置,本篇主要是还是添加一些第三方配置。请跟着脚步开启新的旅行吧。 配置 1.友情链接 在next/_config.yml下搜索1234567# Blog rollslinks_icon: link #图标links_title: 友情链接 #表示Titlelinks_layout: block#links_layout: inlinelinks: #打开 Eirunye: http://eirunye.github.io/ #所需添加的友情链接 Title是表示友情链接的博客名称或者随意你取,后面是链接,冒号后面记得空格 2.添加阅读统计 我们给每篇文章进行添加阅读统计,我是如何配置的呢?如下图查看 1.进入leancloud 2.创建应用 3.进入设置页面 4.应用key 将App ID、App Key 配置到next/_config.yml中leancloud_visitors 1234567leancloud_visitors: enable: true 设置为true 默认为false app_id: #你的App ID,注意冒号后面空格 app_key: #你的App Key,注意冒号后面空格 Dependencies: https://github.com/theme-next/hexo-leancloud-counter-security #设置依赖 security: true #如果您不关心lc计数器中的安全性并且只想直接使用它(没有hexo-leancloud-counter-security插件),请将`security`设置为`false`。 betterPerformance: true#更好的性能 5.在leancloud存储的位置创建Class,必须命名为Counter 6.查看后台统计数据 注:这里主要是创建时需要注意的命名Counter,还有在next/config.yml中的leancloud_visitors配置。 3.添加评论 我们来进行一下文章添加评论系统吧。来设置一些常用的评论系统,我的博客选择的是Valine Valine 在next/_config.yml搜索Valine,进入Valine 官网,也是leancloud官网,进入leancloud 控制台,没有账号密码就进行设置。 1.创建应用 2.进入设置页面 3.应用key 4.在next/_config.yml进行一下配置,大家也可根据自己来设置该评论设置。1234567891011valine: enable: true # 设置为true,默认为false appid: # 将应用key的App ID设置在这里 appkey: # 将应用key的App Key设置在这里 notify: true# 邮箱通知 , https://github.com/xCss/Valine/wiki,默认为false verify: true# 验证码 默认为false placeholder: Just go go ^_^ # 初始化评论显示,根据自己修改,这里默认, avatar: wavatar # 头像风格,默认为mm,可进入网址:https://valine.js.org/visitor.html查看头像设置,这里有许多头像风格,进行设置 guest_info: nick,mail,link # 自定义评论标题 pageSize: 10 # 分页大小,10页就自动分页 visitor: true # 是否允许游客评论 ,进入官网查看设置:https://valine.js.org/visitor.html 5.显示结果 这样就完成了valine评论的配置了,接下来就可以进行评论了,我们还可以在后台查看评论信息。 6.在后台查看评论数据 在valine后台,存储位置中的数据里面创建Class,名称必须为命名为Comment 注:选择valine评论系统是因为支持国内网络,不需要连接外网(翻墙)就可以进行显示评论系统,而且很好管理,页面简单。 来必力评论 接下来我们来进行一下来必力评论系统在NexT上配置。 1.进入来必力官网 2.进入安装页面 3.进入评论系统中的代码管理 将data-uid复制到next/_config.yml中的livere_uid1livere_uid: #you data-uid如图所示 4.显示结果 注:来必力是韩国打造的评论系统,所以可能在国内有时不稳定,可能有时需要翻墙才能进行评论,所以大家在选择的时候,需要注意。 总结1.本章主要讲解的是NexT添加友情链接,这样如需添加,就进行添加。 2.添加评论系统,让我们的文章都可以让读者进行评论,指点江山、探讨知识点,可能出现问题,学习进步。 3.这里进行了两种评论系统的配置,接下来,有空将别的也添加说明一下,一起玩玩别的评论系统看看怎么样,不过这两个也是够了的,推荐Valine,这样我们可以管理阅读统计量。 推荐Hexo配置系列文章 Hexo搭建GitHub博客–初级(一) Hexo搭建GitHub博客–初级(二) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(三) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(四) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(五)]]></content>
<categories>
<category>Hexo</category>
<category>NexT</category>
</categories>
<tags>
<tag>Hexo</tag>
<tag>NexT</tag>
<tag>GitHub</tag>
<tag>Node</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring Boot 连接数据库(五)]]></title>
<url>%2F2018%2F09%2F19%2FSpring-Boot-%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E5%BA%93-%E4%BA%94%2F</url>
<content type="text"><![CDATA[简介本篇我们将学习如何在项目中连接数据库。在一个成熟的项目是离不开数据库的,数据库很好的为我们的项目管理数据,让我们的开发变得简单,我们只需关注数据操作,而无需关注更多的数据库是如何操作的。那么Spring Boot开发的项目是如何连接数据库呢?大家还记得在Spring Boot 配置文件设置(三)配置的时候说到数据库的连接。是的没错上面的例子完完全全都可以进行数据的连接。本篇是以连接MySQL为例,进行连接数据操作。 #安装 在开发之前,我们一定要选择合适的数据库,例如:MySQL、Oracle、SQL Server、SQLite、MongoDB等数据库,本篇将对MySQL进行操作,其他数据库类型,请查看文档,后期可能增加。 1.下载MySQL进行安装 2.配置MySQL环境变量 3.打开CMD,进行验证MySQL是否安装成功 查看版本 1mysql -v 进入MySQL 1mysql -u root -p; 注意: mysql刚刚安装完成时,账户为:root,密码没有,所以直接回车就行 然后可以设置一下密码,或者不设置也无所谓,修改密码如下: 用SET PASSWORD命令: 12mysql -u root -pmysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass'); 或者用mysqladmin,如果无法设置的时候,就用管理者进入设置 1mysqladmin -u root password "newpass" 如果root已经设置过密码,采用如下方法: 1mysqladmin -u root password oldpass "newpass" 创建一个数据库: 12CREATE DATABASE 数据库名;CREATE DATABASE eirunye_springboot_notes; 查看创建的数据库: 1SHOW DATABASES; 连接数据库1.在项目根目录下打开pom.xml文件添加 MySql Maven 依赖 1234567891011121314151617181920212223<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--jpa依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.0.1.RELEASE</version> </dependency> <!--MySQL配置依赖 版本看自己的需求--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies> 2.在application.properties或者在application.yml文件进行配置,本例以application.yml为例 在application.yml添加一下代码。 12345678910111213spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # MySql jdbc Driver # 连接数据库 # eirunye_springboot_notes表示的是你创建的数据库; # useSSL:是否使用SSL证书验证; # characterEncoding:编码格式; # useJDBCCompliantTimezoneShift:是否使用符合JDBC的时区转换; # useLegacyDatetimeCode:是否使用旧版日期时间码; # serverTimezone:选择服务器时间方式; url: jdbc:mysql://127.0.0.1:3306/eirunye_springboot_notes?useSSL=false&requireSSL=false&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: root #本地设置数据库账号 password: 123456 #密码 在application.properties添加一下代码。 1234567891011121314151617spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# 连接数据库# demo表示的是你创建的数据库;# useSSL:是否使用SSL证书验证;# characterEncoding:编码格式;# useJDBCCompliantTimezoneShift:是否使用符合JDBC的时区转换;# useLegacyDatetimeCode:是否使用旧版日期时间码;# serverTimezone:选择服务器时间方式;spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=false&requireSSL=false&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC# 数据库用户名spring.datasource.username=root# 数据库密码spring.datasource.password=123456spring.jpa.hibernate.ddl-auto=update# 是否启用SQL语句的日志记录spring.jpa.show-sql=trueserver.port=8081 3.测试 运行项目若无报错则说明配置已经成功了,接下来就是进行项目编写了。 使用Intellij IDEA操作数据库如果使用的是Intellij IDEA进行开发项目的话,我们也可以这么查看我们的连接,还有相关数据表等等。 如何使用Intellij IDEA连接数据库?1.打开Intellij IDEA的database数据库导航 打开方式一 打开方式二 2.进行连接 3.连接成功 4.注意事项 如果无法连接,那说明账号密码出现问题,还有可能是修改了其他默认的地方。大家请注意。 5.创建表TABLE 创建表 添加字段 插入数据 下载 本篇案例代码下载-码云 本篇案例代码下载-GitHub Spring Boot系列代码-码云 Spring Boot系列代码-GitHub 总结1.一般情况下我在配置连接数据库的时候,推荐大家使用的是在设置文件里面配置在.yml或者是application.properties里面进行设置。 2.接下来的博文讲解如何操作数据库: JPA、MyBatis 3.在使用Spring Boot开发项目中遇到了一些坑,在后期我会不断更新,与大家一起学习。 4.当然了IDEA还有许多操作数据库功能,大家就自己进行操作测试吧。 推荐Spring Boot 系列文章]]></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hexo搭建GitHub博客—打造炫酷的NexT主题--高级(四)]]></title>
<url>%2F2018%2F09%2F15%2FHexo%E6%90%AD%E5%BB%BAGitHub%E5%8D%9A%E5%AE%A2%E2%80%94%E6%89%93%E9%80%A0%E7%82%AB%E9%85%B7%E7%9A%84NexT%E4%B8%BB%E9%A2%98%E2%80%94%E9%AB%98%E7%BA%A7%E2%80%94%E5%9B%9B%2F</url>
<content type="text"><![CDATA[简介上篇Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(三)主要是对NexT整体布局的配置,达到完美喜欢的布局格式。接下来继续对NexT主题博客进行配置,本篇主要是添加一些常用的第三方访问或者服务。请跟着脚步开启新的旅行吧。 配置 1.Math Equations Render Support 数学方程式渲染支持 这里可能有时需要在文章中使用到时数学公式了,在这里设置一下。 12345678math: enable: true #默认为false per_page: true engine: mathjax #两种方式 mathjax / katex mathjax: cdn: //cdn.jsdelivr.net/npm/mathjax@2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML #默认 这里大家根据自己需求 katex: cdn: //cdn.jsdelivr.net/npm/katex@0.7.1/dist/katex.min.css #默认 2.Han Support 支持汉字 设置汉字支持,这里我没配置,选择默认了,如果大家想配置的话就按照以下步骤 1.打开Git Bash Here进入themes/next目录下 1$ cd theme/next 2.获取该汉字支持Git module,执行命令以下命令获得 1$ git clone https://github.com/theme-next/theme-next-han source/lib/Han 3.设置汉字支持 1han: true 4.更新update 12$ cd themes/next/source/lib/Han$ git pull 注: 通过该链接可以查看以上步骤 https://github.com/theme-next/theme-next-han 3.添加图标链接到GitHub 一般在右上角或者左上角,如下我的博客配置。 配置右上角Fork_me_on_GitHub,按以下步骤进行 1.打开Fork_me_on_GitHub链接,里面有许多样式,选择自己喜欢的样式,将其复制下来。 2.打开自己博客项目中的themes/next/layout/_layout.swig文件,搜索<div class="headband"></div> 将复制的内容粘贴到<div class="headband"></div>下面,如下: 注:要修改红色框里面的连接为自己在GitHub上的连接。 配置右上角的Fork_me_on_GitHub 本例的方式和上面的方式一样的步骤,但是获取的连接不同了,本例的连接地址是GitHub Corners 4.将文章底部#标签修改带为带图标的形式 在博客项目中找到/themes/next/layout/_macro/post.swig,搜索 rel="tag",将 #号 换成<i class="fa fa-tag"></i> 原先#号的样式 修改为图标的样式 5.font字体设置 在themes/next/_config.yml搜索font 1234567891011121314151617181920212223font: enable: true #默认false 如果要进行字体修改那么设置为true global: external: true family: Lato #设置字体 下同 size: #字体大小 下同 headings: external: true family: size: posts: external: true family: logo: external: true family: size: codes: external: true family: size: 6.设置背景动画样式 NexT里面有几种动画背景样式canvas_nest、three_waves、canvas_lines、canvas_sphere等 canvas_nest如下图所示 按照以下步骤完成 1.打开Git Bash Here进入自己文件夹下/themes/next文件夹下 1$ cd /themes/next 2.下载安装 canvas_nest module 执行 1$ git clone https://github.com/theme-next/theme-next-canvas-nest source/lib/canvas-nest 在 /themes/next/source/lib查看会看到canvas_nest文件夹 3.在/themes/next/_config.yml设置 1canvas_nest: true 注:canvas_nest连接 注:这里也可以查看设置步骤:canvas_nest设置 three_waves如图所示 打开three_waves 查看设置步骤,这里和canvas_nest步骤是一样的,这里就不写咯。下载完成后,在/themes/next/_config.yml设置 12345three_waves: true#ORcanvas_lines: true#ORcanvas_sphere: true canvas_ribbon只适合 scheme Pisces 这里不测试了,大家可以进入canvas_ribbon安装 123# busuanzi_value_site_uv 表示用户连续点击n篇文章,只记录1次访客数# busuanzi_value_site_pv 表示用户连续点击n篇文章,记录+n次访问量# 这里对应的是2上的id值 8.给每篇文章添加类别和标签 在创建的文章都在source/_post目录下找到,每篇文章添加tags、categories 9.添加进度条 注:添加进度条的话在手机浏览的时候一般情况都有自带的进度条了,例如微信浏览、浏览器浏览等等,这样就出现重复的进度条了,这里看个人是否添加。但是在电脑浏览器浏览却是不错的。本例设置的如下pace-theme-center-atom显示 按照以下步骤进行 注:或者进入这里 Progress配置查看如何配置 1.打开Git Bash Here进入自己文件夹下/themes/next文件夹下1$ cd /themes/next 2.下载安装 Progress module 执行1$ git clone https://github.com/theme-next/theme-next-pace source/lib/pace 在 /themes/next/source/lib查看会看到pace文件夹3.在/themes/next/_config.yml设置 123456789101112131415161718pace: true #设置为true# Themes list:#pace-theme-big-counter#pace-theme-bounce#pace-theme-barber-shop#pace-theme-center-atom#pace-theme-center-circle#pace-theme-center-radar#pace-theme-center-simple#pace-theme-corner-indicator#pace-theme-fill-left#pace-theme-flash#pace-theme-loading-bar#pace-theme-mac-osx#pace-theme-minimal# For example# pace_theme: pace-theme-center-simplepace_theme: pace-theme-center-atom #这里任选其中一种 注大家在这里想用什么样式就自己测试。 10.添加站内搜索 由于可能我需要快速查找相关文章,那么就需要添加站内搜索。 按以下步骤进行 注:或者进入NexT配置站内搜索文档查看如何配置 1.安装站内搜索插件 123$ npm install hexo-generator-searchdb --save或者$ cnpm install hexo-generator-searchdb --save 2.在根目录下的_config.yml添加 123456#表示站内搜索search: path: search.xml field: post format: html limit: 10000 3.在themes/next/_config.yml文件中搜索local_search,进行设置 12345local_search: enable: true #设置为true trigger: auto # auto / manual,auto 自动搜索、manual:按回车[enter ]键手动搜索 top_n_per_article: 1 unescape: true 11.添加打赏功能 查看配置如下 NexT主要提供三种打赏方式分别是微信、支付宝、比特币在themes/next搜索Reward,三个都打开吧 12345# Rewardreward_comment: Donate comment here # 描述wechatpay: /images/wechatpay.jpgalipay: /images/alipay.jpgbitcoin: /images/bitcoin.png 总结本篇我们进行了NexT主题的相关配置,这让我们的博客已经非常漂亮了,接下来我们主要进行添加NexT的评论系统、添加百度、Google检索等高级配置,希望各位读者在配置时遇到问题是,随时评论,我们一起解决相关问题。 推荐大家也可以查看关于Hexo相关文章。 Hexo搭建GitHub博客–初级(一) Hexo搭建GitHub博客–初级(二) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(三) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(四)]]></content>
<categories>
<category>Hexo</category>
<category>NexT</category>
</categories>
<tags>
<tag>Hexo</tag>
<tag>NexT</tag>
<tag>GitHub</tag>
<tag>Node</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hexo搭建GitHub博客—打造炫酷的NexT主题--高级(三)]]></title>
<url>%2F2018%2F09%2F02%2FHexo%E6%90%AD%E5%BB%BAGitHub%E5%8D%9A%E5%AE%A2%E2%80%94%E6%89%93%E9%80%A0%E7%82%AB%E9%85%B7%E7%9A%84Next%E4%B8%BB%E9%A2%98%E2%80%94%E9%AB%98%E7%BA%A7%E2%80%94%E4%B8%89%2F</url>
<content type="text"><![CDATA[简介上面两篇文章让我们人识了Hexo是如何搭建GitHub博客、如何进行部署Hexo到GitHub上、如何进行一些常用的配置例如:Site、URL、Deployment、Date等配置、如何创建文章、创建文章时应该注意哪些事项等等。在Hexo主题官网中有许多主题,大家喜欢什么就进行部署和编辑就好了,大致的思路都是差不多的。本篇让我们正在进入到高级篇学习,如何打造炫酷的NexT主题,让我们的博客更加美化。 安装首先我们先进入到NexT主题官网,看看我们如何安装。这里就直接进行了,大家有空自己可以去看看,本篇主要说的是NexT 6.0版本以上,6.0版本以前就不说了。 1.在项目根目录下,我的项目是myblog,打开Git Bash Here执行以下命令 1$ git clone https://github.com/theme-next/hexo-theme-next themes/next 下载安装完成 2.将Hexo的主题切换为NexT主题在项目根目录下打开_config.yml文件将theme设置为next即: 3.运行命令 12$ hexo clean$ hexo s 看起来还是有点难看啊,接下来进行配置吧。 主题配置接下来就是进入我们博客核心部分了,配置属于自己的主题,让我们一起进入学习吧。 注:这里可能不能全部去设置,大家有属于自己的,可以去研究,欢迎一起分享,可能在后期有遇到好的配置会在接下来的文章编写。 注:一般配置都在theme/next/-config.yml文件下配置 1.修改整体布局 在theme/next/-config.yml找到menu看看自己博客所需的分类 显示如下 在menu_settings如果设置icon: false则无图标,badges: true则标签都会显示数字 123menu_settings: icons: true badges: true #默认是false 注:这里我们需要创建about页面,很简单,同理创建标签tags、归档archives页面一样的方式,所需要创建的名称要与menu相对应,举例说明如下。 12$ hexo new page about #看看menu上还有什么标签没创建就行创建$ hexo new page tags #创建标签等 创建完成之后我们在自己项目查找,如我的是myblog/source/目录下查看新创建好的相关标签页面,里面包含各自的index.md文件,大家可以自行编辑了。 2.Schemes方案设置 12345# Schemes#scheme: Muse #这是 Nex默认版本,黑白主调,大量留白scheme: Mist #Muse 的紧凑版本,整洁有序的单栏外观#scheme: Pisces #双栏 Scheme,小家碧玉似的清新#scheme: Gemini #双子座,也是双栏形式,和Pisces类似 我们来一一测试一下吧,自己喜欢什么风格自行选择。 scheme: Mist scheme: Pisces scheme: Gemini 3.social设置 使用方式: Key: permalink || icon Key表示标签显示,permalink表示URI连接,icon表示图标,自己添加所要显示的, 注:图标库来源 [https://fontawesome.com/icons?from=io] 1234567891011121314151617181920social: GitHub: https://github.com/eirunye || github E-Mail: mailto:yrungen@gmail.com || envelope 简书: https://www.jianshu.com/u/447fdef5fb8f || heartbeat Segmentfault: https://segmentfault.com/blog/iconye_vue || link 知乎: https://www.zhihu.com/people/yi-ke-id/activities || ioxhost #Google: https://plus.google.com/yourname || google #Twitter: https://twitter.com/yourname || twitter #FB Page: https://www.facebook.com/yourname || facebook #VK Group: https://vk.com/yourname || vk StackOverflow: https://stackoverflow.com/ || stack-overflow #YouTube: https://youtube.com/yourname || youtube #Instagram: https://instagram.com/yourname || instagram #Skype: skype:yourname?call|chat || skypesocial_icons: #设置图标是否显示这里 enable: true #表示开启 icons_only: true 只显示图片 transition: true exturl: false 注:在scheme: Pisces该效果不显示? 4.avatar头像设置 12345avatar: url: #/images/avatar.gif #头像图片路径 图片放置在next/source/images rounded: false #是否显示圆形头像,true表示圆形,false默认 opacity: 0.7 #透明度0~1之间 rotated: false #是否旋转 true表示旋转,false默认 5.toc边栏中的目录设置 1234toc: enable: false #是否启动侧边栏 number: true #自动将列表编号添加到toc。 wrap: false #true时是当标题宽度很长时,自动换到下一行 6.Creative Commons 4.0国际许可设置 12# Available: by | by-nc | by-nc-nd | by-nc-sa | by-nd | by-sa | zerocreative_commons: by-nc-sa 7.sidebar侧边栏配置这里选择默认吧。 大家可以去看看里面的属性,有许多相对应的scheme而设置。 8.save_scroll配置 1save_scroll: false # 是否在Cookie中自动保存每个帖子/页面上的滚动位置。 9.excerpt_description 1excerpt_description: true #是否自动摘录主页中的描述作为前导文本。 10.auto_excerpt配置 123auto_excerpt: enable: true #是否自动摘录。不推荐。 length: 150 #这里是说文章开头第一个字到第150个字就显示"阅读全文" 或者是在每篇文章需要显示”阅读全文”,在Markdown里面设置,但是enable: false表示不启动。两种方式选择一个。 1<!-- more --> 11.codeblock代码块配置 1234codeblock: copy_button: enable: true #是否添加复制按钮 show_result: true #是否显示文本复制结果 12.wechat_subscriber微信配置 1234wechat_subscriber: enabled: true #是否启动微信订阅 qcode: /path/to/your/wechatqcode ex. /uploads/wechat-qcode.jpg #设置图片 description: ex. subscribe to my blog by scanning my public wechat account #描述 13.Code Highlight theme 代码突出显示主题 12# Available values: normal | night | night eighties | night blue | night brighthighlight_theme: normal #设置喜欢的模式,默认:normal 14.footer 底部设置 123456789101112footer icon: name: user #设置图标,想修改图标从https://fontawesome.com/v4.7.0/icons获取 animated: true #是否要为图标设置动画,默认为false color: "#66CDAA" #图标颜色 copyright: ©2018 by Eirunye #版权 powered: enable: true #是否显示 Hexo link version: true #是否显示Hexo链接后的Hexo版本信息(vX.X.X) theme: enable: true #是否显示NexT Themelink version: true #是否显示NexT版本信息 15.favicon标签页图标 12345favicon: small: /images/favicon-16x16-next.png #小图标 默认的NexT medium: /images/favicon-32x32-next.png #中图标 默认NexT apple_touch_icon: /images/apple-touch-icon-next.png #苹果触摸图标 safari_pinned_tab: /images/logo.svg #safari固定标签 总结本篇主要讲解的是NexT主题常用的一些配置,接下来我们继续讲解Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(四)。 推荐大家也可以查看关于Hexo相关文章。 Hexo搭建GitHub博客–初级(一) Hexo搭建GitHub博客–初级(二) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(三) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(四)]]></content>
<categories>
<category>Hexo</category>
<category>NexT</category>
</categories>
<tags>
<tag>Hexo</tag>
<tag>NexT</tag>
<tag>GitHub</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hexo搭建GitHub博客--初级(二)]]></title>
<url>%2F2018%2F09%2F01%2FHexo%E6%90%AD%E5%BB%BAGitHub%E5%8D%9A%E5%AE%A2%E2%80%94%E5%88%9D%E7%BA%A7%E2%80%94%E4%BA%8C%2F</url>
<content type="text"><![CDATA[简介上篇我们主要讲的是如何通过Hexo建站,并如何部署到GitHub上Hexo搭建GitHub博客–初级(一)。大家不了解的话再看一下。本篇我们紧接上一篇继续进一步搭建Hexo博客。本篇主要讲解的是如何进行配置我们Hexo的博客,开启旅程吧。 配置我们来进行一些我们常用的配置,在跟myblog目录下_config.yml。可以查看Hexo官网配置模块 网站 参数 描述 title 网站标题 subtitle 网站副标题 description 网站描述 author 您的名字 language 网站使用的语言 timezone 网站时区。Hexo 默认使用您电脑的时区。时区列表。比如说:America/New_York, Japan, 和 UTC 。 例如:我的Hexo设置 12345678# Sitetitle: Eirunye Activity #网站标题subtitle: Notes #网站副标题description: This is MyBlog Notes #详情keywords:author: Eirunye #作者language: zh-CN # 语言timezone: UTC #时区 目录123456789# Directorysource_dir: sourcepublic_dir: publictag_dir: tags #标签archive_dir: archives #归档category_dir: categories #类别code_dir: downloads/codei18n_dir: :langskip_render: 文章查看以下列表,在_config.yml文件下查看相关属性。并进行修改为自己所想要的方式吧,这个就不一一进行分解了。 参数 描述 默认值 new_post_name 新文章的文件名称 :title.md default_layout 预设布局 post auto_spacing 在中文和英文之间加入空格 false titlecase 把标题转换为 title case false external_link 在新标签中打开链接 true filename_case 把文件名称转换为 (1) 小写或 (2) 大写 0 render_drafts 显示草稿 false post_asset_folder 启动 Asset 文件夹 false relative_link 把链接改为与根目录的相对位址 false future 显示未来的文章 true highlight 代码块的设置 自定义 分类&标签这里默认就行,并不影响我们接下来的一些操作。 参数 描述 默认值 default_category 默认分类 uncategorized category_map 分类别名 tag_map 标签别名 自定义 分页在分页中一般情况默认就好,10篇文章一页,不错的。 参数 描述 默认值 per_page 每页显示的文章量 (0 = 关闭分页功能) 10 pagination_dir 分页目录 page 写作创建文章1$ hexo new [layout] <title> 如:创建hello-world1$ hexo new hello-world layout:就是hello-world,如果不添加title,默认就是标题title:hello-world。这里注意一下,如果创建带有中文的路径名称时,生成静态页面hexo g可能会报错。那么可以查看Hexo 部署的时候发生错误解决方案 修改文章创建的文章在source/_posts 文件夹下。我试着对hello-world.md进行修改。修改如下: 添加标签tags、类别categories等等。 12345678---title: Hello Worlddate: 2018-09-01tags:- Hexocategories:- Hexo--- 编写文章在这里写文章,和平时一样完全支持Markdown语法,加油吧。 主题这是我们重点需要修改的东西,为了能达到自己喜欢的布局结构,你需要在这里进行各种配置哦,对博客的美化,还有修改成为自己独特风格的博客。Hexo提供给我们许多模板主题,请查看Hexo主题官网下载自己喜欢的主题,接下来我会重点的讲解我所配置的主题。 总结学习本篇文章我们知道了如何进行配置博客一些常用的功能,如何进行分类,如何创建文章。接下来让我们进入到下一个阶段,主要是如何打造属于自己的博客风格,请进入下一站吧。Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(三)。 推荐大家可以查看关于Hexo相关的文章。 Hexo搭建GitHub博客–初级(一) Hexo搭建GitHub博客–初级(二) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(三)]]></content>
<categories>
<category>Hexo</category>
</categories>
<tags>
<tag>Hexo</tag>
<tag>NexT</tag>
<tag>GitHub</tag>
<tag>Node</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hexo搭建GitHub博客--初级(一)]]></title>
<url>%2F2018%2F09%2F01%2FHexo%E6%90%AD%E5%BB%BAGitHub%E5%8D%9A%E5%AE%A2%E2%80%94%E5%88%9D%E7%BA%A7%E4%B8%80%2F</url>
<content type="text"><![CDATA[简介由于平时自己喜欢记录开发笔记,写一些博客,但是总想自己搭建属于自己的博客,在网上查阅了一下,发现Hexo在GitHub或者是码云上搭建博客非常给力。接下来让我们一起进入Hexo搭建博客学习吧。 Hexo什么是Hexo?Hexo是一个快速、简洁且高效的博客框架。Hexo使用Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 当我们在遇到一个新东西的时候,进入其官网能帮助我们解决很多问题。关于什么是Hexo?还有一些关于Hexo的问题,大家进入Hexo官网进行查看,这里我就不多说咯。 如何使用我们在使用Hexo之前需要准备一些东西,也就是需要安装一些我们必需的。 安装安装所需安装Hexo相当简单。然而在安装前,您必须检查电脑中是否已安装下列应用程序: Node.js Git cnpm 如果npm运行出错就安装cnpm国内镜像 如果没有安装那么就需要安装咯,大家可以看看Vue-cli项目创建如何检查Node.js、Git和cnpm是否安装成功。 安装Hexo所有必备的应用程序安装完成后,即可使用npm或者cnpm来安装Hexo。打开Git Bash Here输入: 123$ npm install -g hexo-cli或者$ cnpm install -g hexo-cli 检查是否安装Hexo完成,查询是否成功,显示hexo-cli版本就说明成功了1$ hexo -V 建站安装一切所需的程序后,我们可以开始建站了,就是创建我们的博客,大家也可以进入Hexo建站官网查看。新建一个文件夹,来管理我们的博客项目,请执行下列命令,Hexo将会在指定文件夹中新建所需要的文件。 运行命令123$ hexo init <folder>$ cd <folder>$ cnpm install 注:folder是表示建站的博客项目名 打开Git Bash Here,输入: 1$ hexo init myblog 再运行以下命令:myblog是我创建博客站。12$ cd myblog$ cnpm install 访问URL我们可以运行看看Hexo创建的博客站是什么样的,运行以下命令 123$ hexo server或者$ hexo s 访问URL1http://localhost:4000/ 好了,到这里你已经学会了通过Hexo创建博客了,接下来我们将会分析如何将我们的博客上传到我们的GitHub服务器上呢?还有我们如何修改我们的博客让我们的博客漂亮而且修改不同的主题来美化我们的博客呢?紧跟着脚步。 上传到GitHubGitHub创建repositories 进入GitHub官网,或者在浏览器输入https://github.com/,如果还没有账号就创建一个账号就好了,这里不教大家怎么注册账号了。登录自己的账号,直接点击new repositories或者进入You repositories再new repositories 进入到创建repositories页面来,这里很重要,一定要注意哦!必须将新建的repository的名字为: You account name.github.io。其他默认就好了 配置GitHub的Repository创建好后。我们在回到本地的Hexo的Myblog项目中,我们用代码编辑器工具打开,我这里用的是Webstorm,找到在项目的根目录下_config.yml找到 deploy标签 在该文件下面添加 注:repository: https://github.com/eirunye/eirunye.github.io.git 是自己刚刚创建You account name.github.io 的repository,在Clone with HTTPS里面,复制粘贴就好咯。冒号后面记得空格哦。branch 后面是master就好了。 1234deploy: type: git repository: https://github.com/eirunye/eirunye.github.io.git #复制过来 branch: master 在_config.yml找到url进行修改为:1url: http://eirunye.github.io # eirunye是我的账号,修改为自己的账号 部署到这里就差不到了,接下来我们需要执行一些命令,将我们的博客部署到GitHub上了,惊喜将会到来了。打开Git Bash Here进入myblog根目录下,首先我们需要安装一下hexo-deployer-git不然可能出现错误,无法部署成功,执行命令行。 1$ cnpm install hexo-deployer-git --save 然后在执行以下命令 注:每次提交时必须执行这三个命令123$ hexo clean$ hexo generate$ hexo deploy 或者简写方式123$ hexo clean$ hexo g$ hexo d 部署成功如下显示 测试我们进行访问https://eirunye.github.io/和 http://localhost:4000/一样的页面说明是已经成功。 总结我们已经从如何使用Hexo到部署上GitHub上,没什么困难。需要我们对一些命令行的操作,还有一些需要我们必须注意的,一定要小心,不然就出错了。当然了,我们还未对Hexo进行一些我们对于博客的美化,还有修改成为自己独特风格的博客。希望我们继续加油吧。接下来的我们将在下一章讲到哦,请查看Hexo搭建GitHub博客–初级(二)。 推荐大家也可以查看关于Hexo相关文章。 Hexo搭建GitHub博客–初级(一) Hexo搭建GitHub博客–初级(二) Hexo搭建GitHub博客—打造炫酷的NexT主题–高级(三)]]></content>
<categories>
<category>Hexo</category>
</categories>
<tags>
<tag>Hexo</tag>
<tag>NexT</tag>
<tag>GitHub</tag>
<tag>Node</tag>
</tags>
</entry>
<entry>
<title><![CDATA[策略模式]]></title>
<url>%2F2018%2F09%2F01%2F%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[简介1.学习本篇博文,我们知道在什么场景下使用策略模式。2.策略模式的优缺点。3.策略模式的思想。 场景 某公司需要每个周五每名员工都要提交周报,在该公司可以提交周报的行为或者“算法”有:邮箱提交、SVN、Git后两种一般是开发部门使用的(刚开始还没有Git提交方式)。 那么在该场景中我们是如何设计业务逻辑呢? 你可能这么想,我用继承的方式多好,在一个超类中都定义有邮箱提交,SVN提交,然后具体角色去继承,然后分别实现这些方法,在进行调用。 但是你是不是忽略了一个问题,一个人事部门的前台小姐姐并不会SVN提交方式,你确实给她也拥有这样的方式了,这不是明显的设计漏洞吗? 而且当你使用这样的方式的时候,如果开发部门经理,又提出新的想法添加了Git提交方式,那么你是不是需要修改了很多代码了呢?牵一发而动全身啊 子类的代码重复到你崩溃了,而且很多部门使用的提交方式不同,我们并不需要知道其他部门的提交方式等等。 所以继承很难解决我们现在的问题了。 那么我们应该怎么设计呢? 答案就是下文 分析什么是策略模式? 策略模式是:定义了算法族,分别封装起来,让其相互替换,相互独立,为达到算法动态修改的角色提供了独立。 为什么需要策略模式? 1.从定义可以看出策略模式是定义了行为“算法”族,将其封装起来,给用户使用的,如果算法改变,那么只需添加或者修改算法方式便能解决问题了,而无需修改其他原有的行为“算法”,因为他们是相互独立的。 2.对客户隐藏具体行为“算法”的实现细节,彼此之间相互独立。 3.我们的场景需要完全符合策略模式,封装了不同的提交行为“算法”。 进入代码分析 我们先来看一下这个UML类图进行分析 具体实现步骤 1.定义一个行为“算法”,该行为“算法”是一个抽象类或者是接口2.各种独立的行为去实现该行为“算法”接口3.定义一个角色,是一个抽象超类或者接口4.超类角色里面有一个封装的行为或者是“算法”的属性5.定义一个方法进行委托该行为或者“算法”6.具体的角色继承超类角色,并实现抽象方法 具体代码分析 SubmissionBehavior 接口12345678910/** * Author Eirunye * Created by on 2018/8/31. * Describe 定义了一个提交的行为称为一簇"算法" * 这可以是一个抽象类或者是一个接口 */public interface SubmissionBehavior { void commit();} MailCommitBehavior.class 1234567891011/** * Author Eirunye * Created by on 2018/8/31. * Describe 邮箱提交行为 */public class MailCommitBehavior implements SubmissionBehavior{ @Override public void commit() { System.out.println("邮箱提交周报行为"); }} SVNCommitBehavior.class 1234567891011/** * Author Eirunye * Created by on 2018/8/31. * Describe SVN 提交行为 */public class SVNCommitBehavior implements SubmissionBehavior{ @Override public void commit() { System.out.println("SVN提交行为"); }} Character.class 抽象类1234567891011121314151617181920212223/** * Author Eirunye * Created by on 2018/8/31. * Describe 定义一个抽象超类角色 * 或者这里也可以是接口 */public abstract class Character { //一个角色有一个这样的行为 public SubmissionBehavior submissionBehavior; public abstract void display(); //完成提交行为 委托 public void completeCommit(){ submissionBehavior.commit(); } //动态设置行为 public void setSubmissionBehavior(SubmissionBehavior submissionBehavior) { this.submissionBehavior = submissionBehavior; }} Coder.class 具体的角色12345678910111213141516/** * Author Eirunye * Created by on 2018/8/31. * Describe 开发员 */public class Coder extends Character{ public Coder() { submissionBehavior = new SVNCommitBehavior(); } @Override public void display() { System.out.println("刚开始使用的周报提交方式是SVN"); }} 测试 Test.class1234567891011121314151617181920212223/** * Author Eirunye * Created by on 2018/8/31. * Describe 测试 */public class Test { public static void main(String[] args) { //创建一个码农提交方式 Character character = new Coder(); character.completeCommit(); //创建一个前台小姐姐提交方式 Character character1 = new ReceptionSister(); character1.completeCommit(); //某天开发部们经理说了以后开发部要统一使用Git方式提交周报了, //那么如下看看发生什么情况 Character character2 = new Coder(); character2.setSubmissionBehavior(new GitCommitBehavior());//动态设置提交方式 character2.completeCommit(); }} 输出结果: 123456C:\Java\jdk1.8.0_161\bin\...SVN提交行为邮箱提交周报行为新增的Git提交周报行为Process finished with exit code 0 下载策略模式案例代码 总结我们学习了策略模式,在生活中是经常运用到的,本例只是一个场景。我们在设计开发的时候一定要“多用组合,少用继承”。针对接口变成,而不针对实现编程。 优点1.策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。通过使用继承抽象超类可以把公共的代码转移到抽象超类父类里面,从而避免重复的代码。2.通过实现接口的方式定义具体的行为“算法”,从而减少子类继承父类相互混淆的行为。3.使用策略模式可以避免使用多重条件转移语句,动态的改变行为,而各个具体角色行为“算法”相互独立。 缺点1.客户端必须知道所有的具体角色,并决定使用哪个算法族,这样在设计开始时是需要花费时间是封装“算法”族的,策略模式只适用于客户端知道所有的算法或行为的情况。2.策略模式造成很多具体的角色,每个具体角色就得创建一个新的类,这样类无法相互联系,所以无法共享。 推荐大家可以到我的博客https://eirunye.github.io进行浏览相关文章,大家一起相互探讨技术。 设计模式系列大家可以了解相关文章。]]></content>
<categories>
<category>设计模式</category>
<category>策略模式</category>
</categories>
<tags>
<tag>Java</tag>
<tag>设计模式</tag>
<tag>策略模式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[观察者模式]]></title>
<url>%2F2018%2F09%2F01%2F%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[简介1.学习本篇博文,我们知道在什么场景下使用观察者模式。2.观察者模式的优缺点。3.观察者模式给我们在今后开发中什么思想。 场景某家科技公司目前在开发一个项目,设计小组需要上报项目的进度给部门经理,主要有两个模块更新原有的业务模块进度和添加新业务的模块完成进度。那么部门经理需要什么途径能最快的得到进度的信息呢?或者可能是项目总监也想参与,去获取到最新的进展情况呢?我们改如何去考虑这个业务呢? 很多时候我们就会想到继承去解决这一情况,毕竟OO编写思想时刻影响这一我们,但是如果某天项目经理出差了,他不想看项目进度了,那我们又改怎么办呢? 我们能不能就是设计一个业务,让能想知道该进度的人,不管什么时候,只要有开发者上报进度的时候就一下他就得到进度的信息,而他无需去关系这个过程。 你是否能想到合适的解决方案呢? 答案就在下文中,你准备好了吗? 问题什么是观察者模式?观察者模式定义一系列对象之间的一对多关系,当一个对象改变、更新状态时,依赖它的都会收到通知改变或者更新。 为什么需要观察者模式?从定义中我们可以知道观察者模式当对象改变时,其他依赖的对象都会收到改变信息的状态。 从本例分析项目经理想知道进度情况,他只需要绑定进度,他就可以知道进度信息了,而无需关心如何操作,如果再增加一个想知道进度信息老板呢?也很容易,也让老板绑定进度信息数据就好了,不想知道的时候就解除绑定,就不在获取进度信息了。 所以在本案例场景中,观察者是我们这个场景非常合适的设计。 如何实现观察者模式?自定义观察者模式实现如下 我们先来看一下这个UML类图进行分析 具体实现步骤 1.构造一个主题Subject或者是一个被观察者Observeable,这是一个接口或者是抽象类 12345678 public interface Subject { //注册观察者 void registerObserver(Observer observe); //解除绑定观察者 void unRegisterObserver(Observer observe); //更新数据 void notifyObservers();} 2.构建一个被观察者实现该主题接口如本例的 DevelopmentProgressData.class,这里是进度信息数据 在registerObserver(Observer o);//方法中将观察者添加到注册列表中 在unRegisterObserve(Observer o);//删除观察者 1234567891011121314151617181920212223242526public class DevelopmentProgressData implements Subject { @Override public void registerObserver(Observer observer) { //将观察者添加到列表中 arrayObserve.add(observer); } @Override public void unRegisterObserver(Observer observer) { int i = arrayObserve.indexOf(observer); if (i >= 0) { //将观察者从列表中解除 arrayObserve.remove(i); } } //通知所以观察者数据更新了 @Override public void notifyObservers() { for (int i = 0; i < arrayObserve.size(); i++) { Observer o = (Observer) arrayObserve.get(i); o.update(completeProgress, updateProgress); } }} 3.构建一个观察者接口Observer 1234public interface Observer { //更新数据 void update(int completeProgress, int updateProgress);} 4.可构建一个展示数据的接口(可忽略) 有展示数据的方法,观察者要实现这个方法 查看本例的 DisplaySchedule 123public interface DisplaySchedule { void display();} 5.定义观察者(模拟该类就是产品经理观察者),需实现接口Observes、DisplaySchedule(可忽略), 、将主题Subject设置为观察者的属性,并将其作为观察者的构造函数如 ProductManagerObserver.class调用 developmentProgressSubject.registerObserver(this);将观察者注册到观察列表中12345678910111213141516171819202122232425public class ProductManagerObserver implements Observer, DisplaySchedule { private int completeProgress;//完成进度 private int updateProgress;//更新进度 //将主题当成观察者的属性 private Subject developmentProgressSubject; public ProductManagerObserver(Subject developmentProgressSubject) { this.developmentProgressSubject = developmentProgressSubject; //注册该观察者 developmentProgressSubject.registerObserver(this); } @Override public void display() { System.out.println("产品经理管理者显示当前数据 完成进度为: " + completeProgress + "更新修改进度为:" + updateProgress); } @Override public void update(int completeProgress, int updateProgress) { this.completeProgress = completeProgress; this.updateProgress = updateProgress; display(); }} 测试 RunTest.class1234567891011121314public class RunTest { public static void main(String[] args) { DevelopmentProgressData developmentProgressData = new DevelopmentProgressData(); ProductManagerObserver productManagerObserver = new ProductManagerObserver(developmentProgressData); ProjectManagerObserver projectManagerObserver = new ProjectManagerObserver(developmentProgressData); developmentProgressData.setCurrentData(34, 45); //当项目经理出差了,不观察项目进度了就取消订阅了 developmentProgressData.unRegisterObserver(projectManagerObserver); //当前只有产品经理获取到数据 developmentProgressData.setCurrentData(46, 90); }} 输出结果 123456C:\Java\jdk1.8.0_161\bin\...产品经理管理者显示当前数据 完成进度为: 34更新修改进度为:45项目管理真显示当前数据完成进度为: 34更新修改进度为:45产品经理管理者显示当前数据 完成进度为: 46更新修改进度为:90Process finished with exit code 0 根据java.util.observerable包下的Observerable.class实现观察者模式功能实现如下 具体实现步骤 1.首先观察者需要实现java.util.Observer,然后将其被观察者=>java.util.Observaerable作为其观察者的构造函数 、通过observeable.addObserver(this)添加观察者 123456789101112131415161718192021222324252627 public class BossMngObserver implements Observer, DisplayIllustrate { private Observable observable; private int valuableProductNum; //库存有贵重产品 private int normalProductNum; //普通产品 public BossMngObserver(Observable observable) { this.observable = observable; observable.addObserver(this); } @Override public void disPlay() { System.out.println("总经理观察数据改变:贵重产品数量: " + valuableProductNum + "普通产品数量: " + normalProductNum); } //<2>、实现 Observer更新数据方法 //看本例的包下的observe的三个类 @Override public void update(Observable o, Object arg) { if (o instanceof InventoryData) { InventoryData inventoryData = (InventoryData) o; this.valuableProductNum = inventoryData.getValuableProductNum(); this.normalProductNum = inventoryData.getNormalProductNum(); disPlay(); } }} 2.被观察者需要继承java.util.Observerable, 、然后先调用setChanged()方法 、在进行调用notifyObserves()更新数据 1234567891011121314151617 public class InventoryData extends Observable { private int valuableProductNum; //库存有贵重产品 private int normalProductNum; //普通产品 public void setCurrentData(int valuableProductNum, int normalProductNum) { this.valuableProductNum = valuableProductNum; this.normalProductNum = normalProductNum; statusChange(); } private void statusChange() { //先调用 setChanged(); setChanged(); notifyObservers(); }} 测试3.Test.class1234567891011121314151617181920public class Test { public static void main(String[] args) { InventoryData inventoryData = new InventoryData(); ValuableInfoMngObserver io = new ValuableInfoMngObserver(inventoryData); //io.deleteObserve(); inventoryData.setCurrentData(20, 30); NormalInfoMngObserver no = new NormalInfoMngObserver(inventoryData); //no.deleteObserver(); inventoryData.setCurrentData(15, 27); BossMngObserver bossMngObserver = new BossMngObserver(inventoryData); inventoryData.setCurrentData(10, 50); }} 下载观察者模式案例代码 总结观察者模式的让我们知道了在设计开发的时候一定要“多用组合,少用继承”。 我们设计开发是应该是针对接口变成,而不针对实现编程。 在java.util.*下的Observer和Observable可以实现观察者,但是Observable是一个类,这样我们是不违背了“多用组合少用继承”的OO编程思想,是的没错在java.util.Observable类违背了该规则。 推荐大家可以到我的博客https://eirunye.github.io进行浏览相关文章,大家一起相互探讨技术。 设计模式系列大家可以了解相关文章。]]></content>
<categories>
<category>设计模式</category>
<category>观察者模式</category>
</categories>
<tags>
<tag>Java</tag>
<tag>设计模式</tag>
<tag>观察者模式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring Boot 项目如何搭建(四)]]></title>
<url>%2F2018%2F08%2F22%2FSpring-Boot-%E9%A1%B9%E7%9B%AE%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA-%E5%9B%9B%2F</url>
<content type="text"><![CDATA[简介Spring Boot如何搭建完整项目在项目开发中我们一般情况下提供给APP或者网站应用的接口一般的请求方式是GET、POST、PUT等等,那么接下来我们将通过一个简单的案例来进行分析,带领大家进入Spring Boot项目的开发,创建(层级分明,思路请析)、编码,主要是 搭建项目 等,如何搭建一个Spring Boot开发项目呢?。 项目结构创建项目目录如下,包层级分明一般如下图所示: 在src目录下的main/java里面的创建的包级目录,进行分析如下: 1234567891011/----------------------------------|-- controller:主要是控制器,通过注解@RestController,Spring4之后新加的注解,原来返回json数据格式,通@RequestMapping,设置访问URI。|-- enums: 主要是封装返回码的提示。|-- exception: 主要是封装异常打印输出。|-- handle: 捕捉异常处理|-- model:实体类|-- repository:Jpa使用封装,假如使用MyBatis的会进行其封装,后期文章会说明到MyBatis|-- service:逻辑处理封装,返回数据给Controller类|-- utils:封装一些工具类|-- 其他:在开发中还有一些需要创建其他包,根据自己需求。\------------------------------------ 主要代码分析这里主要是写一个简单的事例进行参考包层级之间的调用,各个层次的封装,达到一个完整的开发Spring Boot项目模板。 代码注解如下: 1.EirunyeController.class,这里进行一些常用的请求GET、POST、PUT等案例测试,代码没用贴全,请查看EirunyeController.class。12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849@RestController@RequestMapping("/Eirunye") //设置请求的父级标签URIpublic class EirunyeController { @Autowired //注:Autowired是按类型进行装配,可获取它所装配类的属性 EirunyeService eirunyeService; /** * 访问返回数据 * * @return JSON EiBean * @throws Exception 出现异常处理 */ @GetMapping(value = "/get/bean")//url===>http:localhost:8091/Eirunye/get/bean public Result<EiBean> getEiBeanData() throws Exception { return eirunyeService.getEiBeanData(); } /** * POST请求 * @param eiBean * @return * @throws Exception */ @PostMapping(value = "/save/bean") //url===>http:localhost:8091/Eirunye/save/bean public Result<EiBean> saveEiBeanData(@Valid EiBean eiBean) throws Exception { return eirunyeService.saveEiBeanData(eiBean); } /** * PUT请求 * @param eiBean * @return * @throws Exception */ @RequestMapping(value = "/put/eiBean", method = RequestMethod.PUT)//url===>http:localhost:8091/Eirunye/put/eiBean public Result<EiBean> putEiBeanData(@Valid EiBean eiBean) throws Exception{ return eirunyeService.putEiBeanData(eiBean); } /** * DELETE 请求案例 * @param id id * @return JSON EiBean * @throws Exception 异常处理 */ @DeleteMapping(value = "/delete/Bean/{id}")//url===>http:localhost:8091/Eirunye/delete/Bean/1 public Result<EiBean> deleteEiBeanDataById(@PathVariable("id")Integer id) throws Exception{ return eirunyeService.deleteEiBeanDataById(id); }} 2.EirunyeService.class @Service 用于标注业务层组件:将当前类注册为Spring的Bean123456789101112131415@Service // 用于标注业务层组件:将当前类注册为Spring的Beanpublic class EirunyeService { /** * @return Result<EiBean> * @throws EirunyeException 异常处理 */ public Result<EiBean> getEiBeanData() throws EirunyeException{ EiBean eiBean = new EiBean(); eiBean.setName("Eirunye"); eiBean.setAge(19); eiBean.setHobby("Java Spring Boot"); return ResultUtil.globalInfo(ResultEnum.SUCCESS,eiBean); }} EiBean.class 123456789public class EiBean { private String name; private int age; private String hobby;//get/set...} *ResultUtil.class表示返回JSON数据封装123456789101112131415161718192021/** * Author Eirunye * Created by on 2018/8/24. * Describe ResultUtil 返回结果封装 */public class ResultUtil { public static Result globalInfo(ResultEnum resultEnum, Object object) { Result result = new Result(); result.setCode(resultEnum.getCode()); result.setMsg(resultEnum.getMsg()); result.setData(object); return result; } public static Result error(Integer code,String msg){ Result result = new Result(); result.setCode(code); result.setMsg(msg); return result; }} ResultEnum枚举,进行封装符合字符提示 12345678910111213141516171819202122232425/** * Author Eirunye * Created by on 2018/8/24. * Describe ResultEnum */public enum ResultEnum { UNKNOWN_ERROR(-1, "UNKNOW ERROR"),//返回失败 SUCCESS(0, "SUCEESSS"), ///返回成功 ///这里大家定义自己的返回系列 ; private Integer code; //返回码 0表示成功,1表示失败,-1未知错误 private String msg; ResultEnum(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; }} *EirunyeException.class这里表示封装的一次处理。添加异常处理,这里只是简单的提示,后期会讲解到Spring Boot如何优雅的封装异常。 12345678910111213141516171819/** * Author Eirunye * Created by on 2018/8/24. * Describe ResultEnum */public class EirunyeException extends RuntimeException { private Integer code; public EirunyeException(ResultEnum resultEnum) { super(resultEnum.getMsg()); this.code = resultEnum.getCode(); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; }} 常用的注解 参数注解官方文档 这里主要讲解的是常用的注解方法。需要更多的情况官方文档。 注解 解释说明 @RestController 表示的是:@ResponseBody + @Controller,作用于类,返回默认为JSON数据 @Controller 表示视图可以解析jsp、xml等文档,需要在方法中添加@ResponseBody,返回JSON @ResponseBody 作用于方法中,如果类中有@RestController,就不需要添加@ResponseBody @RequestMapping("/Eirunye") 作用于类中是表示设置请求的父级标签URI,每个URI必须带父级标签如:http://localhost:8080/Eirunye/xxxx作用于方法是表示默认为GET请求@RequestMapping(value = "/put/eiBean",method = RequestMethod.PUT) 表示PUT请求,如图RequestMethod.png 常用的请求方法 @GetMapping(value = "/bean") 表示GET请求@GetMapping(value = "/get/hello/{id}")表示参数id,看上面代码引用@PathVariable("id") @PathVariable("xx") 表示在方法的参数中引用,获取URL中{xx}中的数据,xx表示的字段相同,看以上代码id @RequestParam 表示请求参数,看以上代码(@RequestParam("name") String name, @RequestParam("age") int age) @PostMapping(value = "/save/bean") 表示POST请求,@RequestMapping(value = "/save/bean", method = RequestMethod.POST)简写,参数(@Valid EiBean eiBean)或者params形式等 @DeleteMapping(value = "delete/Bean/{id}") 表示DELETE请求,@RequestMapping(value = "delete/Bean/{id}", method = RequestMethod.DELETE) @Service 用于标注业务层组件:将当前类注册为Spring的Bean,操作数据库一般在这里进行 注:在后期文章遇到新的注解会说明到。 测试在接下来会有一篇博文会详细讲解在开发中我们如何进行测试(#),这里只进行一些简单测试 在这里我们只需要进行一下访问结果,如下: Postman请求如下:Postman请求有全部满足要求的请求,切换请求方法即可。 IDEA请求如下: 下载 本篇案例代码下载-码云 本篇案例代码下载-GitHub Spring Boot系列代码-码云 Spring Boot系列代码-GitHub 总结1.学习本篇,我们知道如何搭建一个好的项目工程,常用的功能架构 2.我们在搭建项目的时候要尽可能考虑到我们需要的处理逻辑,选型,然后进行封装,这只是一个简单的项目搭建,在正常情况下,假如添加MyBatis或者其他数据库操作时,最好分成处理,这里我们阅读代码非常方便,异常处理非常重要,我们能快速定位错误位置等等。 3.打家可以通过该案例来进行搭建项目,符合自己的代码编写项目操作。 4.本案例代码大家完全可以引用到自己的项目中进行封装开发。 推荐 我的博客大家可以到我的博客https://eirunye.github.io进行浏览相关文章,大家一起相互探讨技术。 Spring Boot 系列如果大家想了解更多的Spring Boot相关博文请进入我的Spring Boot系列博客栈]]></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring Boot 配置文件设置(三)]]></title>
<url>%2F2018%2F08%2F20%2FSpring-Boot-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AE%BE%E7%BD%AE-%E4%B8%89%2F</url>
<content type="text"><![CDATA[简介上篇我们做了一些简单的运行文件的配置,本篇带领大家来认识常用的一些配置,当然了关于Spring Boot 这些配置太多太多了,如果想了解更多的话直接上官网参考一下,了解相关案例如本篇的配置。 application.properties配置官方指南参考。 Spring Boot有以下方式配置application.properties配置在 IntelliJ IDEA 开发工具中创建项目的时候,默认的配置文件是application.properties,接下来我们就学习一下然后配置一些我们在开发中经常用到的配置项,进入带领我们揭开Spring Boot 项目的神秘的面纱。在下面的文档中我会在每个配置中进行注解,这样能更好的了解,当然官方文档有我们所需的全部配置,大家如果项目开发中有需求那么可以进入application.properties配置官方指南参考。 英: Appendix A. Common application properties 1234567Various properties can be specified inside your application.properties file, inside your application.yml file, or as command line switches.This appendix provides a list of common Spring Boot properties and references to the underlying classes that consume them.[Note]Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list.Also, you can define your own properties.[Warning]This sample file is meant as a guide only. Do not copy and paste the entire content into your application. Rather, pick only the properties that you need. 译: 官方指南者三段话概况了今天我们要讲得内容:12345可以在application.properties文件中,application.yml文件中或命令行开关中指定各种属性。 本附录提供了常用Spring Boot属性的列表以及对使用它们的基础类的引用。[注意]属性贡献可以来自类路径上的其他jar文件,因此您不应将此视为详尽的列表。 此外,您可以定义自己的属性。[警告]此示例文件仅供参考。 不要将整个内容复制并粘贴到您的应用程序中。 相反,只选择您需要的属性。 所以我们在添加某些配置属性的时候,一定要根据自己的需要来添加,不然有时出错了,不知道哪里找问题。 常用的application.properties配置一般情况下在src目录下的/main/resource文件夹中新建application.properties文件,目录结构如下:1234|--src |--main |--resources |--application.properties 我们接下来编写一些常用的属性配置,大家在开发中需要到哪些就直接去查看一下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164#启用调试日志。debug=false#启用跟踪日志。trace=false#--------------------------------------# LOGGING 日记#--------------------------------------# 日志配置文件的位置。 例如,Logback的classpath:logback.xmllogging.config=classpath:logback.xml# 日志文件名(例如,`myapp.log`)。名称可以是精确位置或相对于当前目录。logging.file=property.log# 最大日志文件大小。 仅支持默认的logback设置logging.file.max-size=10MB# 日志文件的位置。 例如,`/ var / log`。logging.path=/var/log#---------------------------------# AOP#---------------------------------# 使用AOP 切面编程spring.aop.auto=true#是否要创建基于子类的(CGLIB)代理(true),而不是基于标准Java接口的代理(false)spring.aop.proxy-target-class=true#--------------------------------# Email#--------------------------------# 编码格式spring.mail.default-encoding=UTF-8# SMTP服务器主机spring.mail.host=smtp.property.com#SMTP服务器端口spring.mail.port=7800# 登录SMTP用户名spring.mail.username=property# 登录SMTP密码spring.mail.password=123456#--------------------------------# WEB 属性配置#--------------------------------# 服务器应绑定的网络地址server.address=127.0.0.1# 是否启用了响应压缩server.compression.enabled=false# 连接器在关闭连接之前等待另一个HTTP请求的时间。 未设置时,将使用连接器的特定于容器的默认值。 使用值-1表示没有(即无限)超时server.connection-timeout=2000# 错误控制器的路径server.error.path=/error# 是否启用HTTP / 2支持,如果当前环境支持它。server.http2.enabled=false# 服务器端口默认为:8080server.port=8084# SP servlet的类名。server.servlet.jsp.class-name=org.apache.jasper.servlet.JspServlet# 主调度程序servlet的路径。server.servlet.path=/home# 会话cookie名称server.servlet.session.cookie.name=propertydemo#------------------------------# HTTP encoding#------------------------------# HTTP请求和响应的字符集。 如果未明确设置,则添加到“Content-Type”标头。spring.http.encoding.charset=UTF-8# 是否启用http编码支持。spring.http.encoding.enabled=true#--------------------# MULTIPART (MultipartProperties)#--------------------# 是否启用分段上传支持spring.servlet.multipart.enabled=true# 上传文件的中间位置spring.servlet.multipart.location=/log# 最大文件的大小spring.servlet.multipart.max-file-size=1MB# 最大请求大小spring.servlet.multipart.max-request-size=10MB# 是否在文件或参数访问时懒惰地解析多部分请求。spring.servlet.multipart.resolve-lazily=false#--------------------------------------------# SPRING SESSION JDBC (JdbcSessionProperties)#--------------------------------------------# cron表达式用于过期的会话清理作业spring.session.jdbc.cleanup-cron=0 * * * * *# 数据库模式初始化模式spring.session.jdbc.initialize-schema=embedded# 用于初始化数据库模式的SQL文件的路径spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql# 用于存储会话的数据库表的名称spring.session.jdbc.table-name=SPRING_SESSION#----------------------------------# MONGODB 数据库配置#----------------------------------# 数据库名称spring.data.mongodb.database=demo# host 配置spring.data.mongodb.host=127.0.0.1# 登录用户名spring.data.mongodb.username=property# 登录密码spring.data.mongodb.password=123456# 端口号,自己根据安装的mongodb端口配置spring.data.mongodb.port=9008# 要启用的Mongo存储库的类型spring.data.mongodb.repositories.type=auto# 连接数据urispring.data.mongodb.uri=mongodb://localhost/test#---------------------------------------# DATASOURCE 数据库配置#---------------------------------------# MySql jdbc Driverspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# 连接数据库# demo表示的是你创建的数据库;spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=false&requireSSL=false&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC# 数据库用户名spring.datasource.username=root# 数据库密码spring.datasource.password=123456#-----------------------------------# Jpa使用#-----------------------------------# 目标数据库进行操作,默认情况下自动检测。可以使用“databasePlatform”属性设置。#spring.jpa.database= demo1# 要操作的目标数据库的名称,默认情况下自动检测。 也可以使用“Database”枚举来设置。#spring.jpa.database-platform=DEMO# DDL模式 一般有这几种方式,Spring Boot会根据是否认为您的数据库是嵌入式的,为您选择一个默认值# update: 更新架构时,使用;spring.jpa.hibernate.ddl-auto=update# 是否启用SQL语句的日志记录spring.jpa.show-sql=true#----------------------------------------# TESTING PROPERTIES#----------------------------------------# 要替换的现有DataSource的类型spring.test.database.replace=any# MVC打印选项spring.test.mockmvc.print=default# ---------------大家查看文档进行配置,不一一列举了----------------------# 各个属性注解在查看常用配置文件application.properties中# FREEMARKER# DEVTOOLS配置# SPRING HATEOAS# HTTP message conversion# GSON# JDBC# JEST (Elasticsearch HTTP client) (JestProperties)# CASSANDRA (CassandraProperties)# --------------------------等等---------------------------------- 查看常用配置文件application.properties 自定义属性 由于有时为了方便项目的开发维护,我们可能需要到自定义配置属性,接下来我们也来搞一下自定义属性配置。 在application.properties自定义配置属性: 1.application.properties添加: 12345#--------------------------------# 自定义属性#--------------------------------com.eirunye.defproname="root"com.eirunye.defpropass="123456" 2.在DefPropertyController.class引用 1234567891011121314@RestControllerpublic class DefPropertyController { @Value("${com.eirunye.defproname}") private String defProName; @Value("${com.eirunye.defpropass}") private String defProPass; @RequestMapping(value = "/defproprety") public String defPropretyUser() { return "这个自定义属性名为: " + defProName + ", 密码为:" + defProPass; }} 注意在获取自定义属性时一定要严格按照配置文件来获取并且Value里面的字符串一定是$+花括号{***},花括号里面的***表示为:application.properties里面自定义的字符串,所以本例就是表示为:@Value("${com.eirunye.defproname}") 。如果application.properties有自定义为test.ok="haha",同样的获取方式为:@Value("${test.ok}") 3.测试这里暂未使用测试代码的方式,后面的文章会讲到 IntelliJ IDEA访问1http://localhost:8084/defproprety Postman访问 通过Bean的形式获取 假如我们遇到这样情况,自定义属性多,然后每个都是通过@Value(${""})方式的话可能会很容易出错,那么我们可以采用以下方式。 1.新建一个Properties.class添加@ConfigurationProperties(prefix = "com.eirunye")//表示的是通过自定义属性查找,如果自定义是:test.ok=haha,则该这样表示:@ConfigurationProperties(prefix = "test") 1234567891011121314151617@ConfigurationProperties(prefix = "com.eirunye")//添加该注解public class Properties { private String defproname; private String defpropass;// get/set方法 public String getDefproname() { return defproname; } public void setDefproname(String defproname) { this.defproname = defproname; } public String getDefpropass() { return defpropass; } public void setDefpropass(String defpropass) { this.defpropass = defpropass; }} 2.在controller包下创建 DefBeanPropertyController.class 12345678910@RestControllerpublic class DefBeanPropertyController { //通过 Autowired注解来获取到 Properties属性,注:Autowired是按类型进行装配,可获取它所装配类的属性 @Autowired Properties properties; @RequestMapping(value = "/bean/defproperty") public String getDefBeanProperties() { return "这是通过Bean注解的方式获取属性: " + properties.getDefproname() + ",密码为: " + properties.getDefpropass(); }} 3.在项目的入口文件Application添加注解@EnableConfigurationProperties最后加上包名不然可能找不到扫描文件如:@EnableConfigurationProperties({com.eirunye.defpropertys.bean.Properties.class})。 12345678@SpringBootApplication@EnableConfigurationProperties({com.eirunye.defpropertys.bean.Properties.class})//添加注解bean的扫描文件public class DefpropertysApplication { public static void main(String[] args) { SpringApplication.run(DefpropertysApplication.class, args); }} 4.测试IntelliJ IDEA访问 创建文件xxx.properties文件方式 我们可以自己创建一个自定义属性的文件如本例def.properties,(注:一般都是以 .properties 文件结尾) 1.添加自定义def.properties配置如下: 1234567#--------------------------------# 自定义属性#--------------------------------# 用户名com.eirunye.defineuser="property"# 年龄com.eirunye.defineage=20 2.创建 DefineProperties.class 12345678910111213141516171819202122@Configuration@ConfigurationProperties(prefix = "com.eirunye")//添加注解 ConfigurationProperties "com.eirunye"表示的是自定义属性@PropertySource("classpath:defines.properties")// 添加注解 PropertySource 该注解能根据路径扫描到我们的文件public class DefineProperties {// 这里可以通过@Value("${}")方式添加,我已经屏蔽掉了,直接通过ConfigurationProperties注解的方式// @Value("${com.eirunye.defineuser}") private String defineuser;// @Value("${com.eirunye.defineage}") private int defineage;// get/set方法 public String getDefineuser() { return defineuser; } public void setDefineuser(String defineuser) { this.defineuser = defineuser; } public int getDefineage() { return defineage; } public void setDefineage(int defineage) { this.defineage = defineage; }} 3.在DefinePropertiesController.class引用 123456789@RestControllerpublic class DefinePropertiesController { @Autowired DefineProperties defineProperties; @RequestMapping(value = "define/Properties") public String getDefinePropertiesData(){ return "新建文件自定义属性姓名:"+defineProperties.getDefineuser()+",新建文件自定义属性年龄:"+defineProperties.getDefineage(); }} 4.别忘了在Application里面添加配置@EnableConfigurationProperties,即:@EnableConfigurationProperties({com.eirunye.defpropertys.bean.Properties.class,com.eirunye.defpropertys.bean.DefineProperties.class}) 5.测试 1http://localhost:8084/define/Properties 下载application.properties案例demo application.yml配置由于application.properties配置有点繁琐,简洁是我们非常喜欢的,那么在Spring Boot程序里面当然也是可以用.yml文件来配置的,接下来让我们进入对.yml文件的一些相关配置吧,官方文档Using YAML Instead of Properties。 常见的配置首先在src目录下的/main/resource文件夹中新建application.yml、application-dev.yml、application-prod.yml三个文件,删除application.properties文件,目录结构如下:123456|--src |--main |--resources |--application.yml |--application-dev.yml |--application-prod.yml 相信很多人要骂街了,这什么情况不是说,yml配置很给力吗?怎么还有创建那么多文件,这不是比上面的 application.properties配置还多此一举吗?莫急接下来让我们来看看.yml的配置之后,我相信你肯定在项目中会喜欢用它。 常见的配置属性如下: 【注意: 这里属性之间的间隔必须按照要求而来,如:冒号后面要空格】application.yml 配置 123456789101112131415161718192021222324252627spring: profiles: active: dev #引用 application-dev.yml文件,这里我们可以改为 prod,表示引用application-prod.yml文件 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/demo?useSSL=false&requireSSL=false&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: root password: 12346 jpa: hibernate: ddl-auto: update show-sql: true data: mongodb: host: 127.0.0.1 uri: mongodb://localhost/test username: root password: 123456 database: test test: database: replace: any mockmvc: print: default servlet: multipart: enabled: true application-dev.yml 可以当成正式服务器端口 12server: port: 8084 application-prod.yml 可以当成测试服务器端口 12server: port: 8080 查看更多.yml配置 自定义yml配置 在application.yml配置 1.和上面的application.properties类似,但是需要注意的是格式问题1234com: eirunye: ymlname: ymlroot ymlpass: yml123456 2.通过@Value("${com.eirunye.ymlname}")获取 12345678910111213@RestControllerpublic class YmlPropertiesController{ @Value("${com.eirunye.ymlname}") private String ymlname; @Value("${com.eirunye.ymlpass}") private String ymlpass; @RequestMapping(value = "yml/proprety") public String getYmlPropreties() { return "这个自定义属性名为: " + ymlname+ ", 密码为:" + ymlpass; }} 3.测试—访问: 1http://localhost:8084/yml/proprety 通过Bean方式获取和application.properties方式一样 1.创建YmlPropertyBean.class 1234567@ConfigurationProperties(prefix = "com.eirunye")//添加该注解public class YmlPropertyBean { private String ymlname; private String ymlpass;// get/set方法.....} 2.在 YmlPropertyBeanController.class引用 123456789@RestControllerpublic class YmlPropertyBeanController { @Autowired YmlPropertyBean propertyBean; @RequestMapping(value = "/bean/ymlproperty") public String getYmlPropertyData(){ return "这个bean自定义属性名为: " + propertyBean.getYmlname()+ ", 密码为:" + propertyBean.getYmlpass(); }} 3.测试-访问 1http://localhost:8084/bean/ymlproperty 下载.yml案例demo 总结 1.本篇主要讲得配置文件,到此就结束了,在开发中这是我们经常用到。 2.在本篇有些相关配置,本例的代码实例还没涉及到,接下来会继续结合相关的配置案例继续更新。 3.相信大家也有所掌握。]]></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring Boot 项目创建(二)]]></title>
<url>%2F2018%2F08%2F14%2FSpring-Boot-%E9%A1%B9%E7%9B%AE%E5%88%9B%E5%BB%BA-%E4%BA%8C%2F</url>
<content type="text"><![CDATA[如何创建Spring Boot 项目?接下来我们将学习如何创建第一个Spring Boot项目 hello Spring Boot! 呢? 我们将以 IntelliJ IDEA 开发工具为例创建Spring Boot项目 如果还没下载过IntelliJ IDEA,那么打开下载即可。 IntelliJ IDEA 破解码,这个之前是可以用的,谢谢作者分享,不知道还能不能用。 一切准备完成后,开始使用开发之旅吧。 IDEA创建Spring Boot项目流程如图所示 File —–> New ——> Project… 选择 Spring Initializr Project Matedata 选择Web —–> Web —–> Spring Boot版本(默认即可) 选择项目保存的文件夹 创建完成 查看项目目录12345678910|--src:源代码文件 |--main |--java:项目的代码Java代码编写在这里 |--resources:一般是配置文件等 |--static:静态资源文件(js、css、img) |--template:模板(.html等) |--application.properties: 配置文件 |--test:主要是用于测试|--target:是项目打包生成的.jar文件在这里|--pom.xml:是添加依赖文件、版本号、打包设置为Jar或者War等 (如下图:pom.xml.png) 查看 pom.xml 编辑配置application.properties 文件 一般需要配置一些端口,数据库连接、编码方式等,如下图123456#设置端口server.port=8084#session失效时间server.session-timeout=3000#编码方式server.tomcat.uri-encoding=utf-8 程序的入口文件 编写简单的例子 如下图所示:12345创建一个controller 包,在包下创建 HelloController.class添加注解: @RestController ---->默认Json格式数据 @GetMapping(value = "/hello") 'hello':表示访问路径 解下来的文章会讲述到更多的注解...... 测试运行是否正常 如下图 访问测试:1浏览器访问 1IDEA 自带的访问(推荐) 1Postman 访问(推荐) 下载安装 Postman 本例项目下载demo 总结 在IDEA开发工具中构建Spring Boot非常简单,推荐大家使用IDEA(Eclipse创建项目在这里不举例了)。 接下来讲解的是Spring Boot 的相关配置。]]></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spring Boot 认识(一)]]></title>
<url>%2F2018%2F08%2F14%2FSpring-Boot-knowledge-1%2F</url>
<content type="text"><![CDATA[简介最近开发项目一直用到了Spring Boot脚手架工具,让我们来认识它一下吧,进入Spring Boot开发之旅。 谈谈对SpringMVC 、Spring Boot 、SpringCloud 认识,这样帮助我们快速入门学习该部分的内容。 认识Spring Boot Spring Boot 是由 Pivotal 团队提供的全新框架,其目的是用来简化新 Spring 应用的初始搭建以及开发过程。 Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. 在开发中给了我们很大的便利,不用进行过多的配置,和繁琐的设置,非常方便我们学习了开发项目。 总结一下几点非常棒: 12345678910入门简单开发效率高无须额外的配置监控运行过程等精心策划的依赖项具有丰富的SQL和NoSQL支持构建任何东西 - REST API,WebSocket,Web,Streaming,Tasks等支持嵌入式运行时支持 - Tomcat,Jetty和Undertow简化安全性... Spring Boot 和Spring MVC区别 Spring MVC 123Spring MVC 是Spring的一个模块,是一种解决Web开发应用的框架。这种开发模式层次分明,轻度解耦,为Web应用提供了许多模板,减轻了开发难度,但是Spring都要进行复杂的XML、JavaConfig等资源的配置。Spring MVC 框架使用IOC对控制逻辑和业务提供了全面的分离,从而很好的解决了复杂错乱的问题。Spring MVC 需要过分的配置,导致花费更多的时间,效率低下。 Spring Boot 123456789Spring Boot 创建完成后,提供了许多复杂的配置,降低了开发的进度和成本。例如: Spring Boot创建完成后,可以集成JDBC、Redis、MySQL、JackJson等等这些配置,让我们在开发过程中收益,只要关注业务,完成相关业务即可,从而不必担心配置的问题。Spring Boot 可以直接独立运行,简单即可。Spring Boot 是Spring的一套快速配置脚手架框架,Spring Boot 提供了一系列的相关配置,还有相关了Maven依赖,完全可靠,开发功能和Spring MVC相关功能兼容。Spring Boot 提供了基于http、ssh、telnet等对运行时的项目进行监控。Spring Boot 降低学习成本,快速入手开发项目,没有繁琐的配置(这是是广大开发者梦寐以求的)Spring Boot 可以使用Spring Initializr在几秒钟内开始使用Spring Boot 开发人员生产力工具,例如实时重新加载和自动重启Spring Boot 适用于您最喜欢的IDE - Spring Tool Suite,IntelliJ IDEA和NetBeans... Spring Boot 和Spring Cloud区别Spring Cloud 1234Spring Cloud 是一种云端分布式架构的解决框架(称为微服务)。Spring Cloud 是一个基于 Spring Boot 框架实现云微服务应用开发的一套工具.Spring Cloud 主要是专注于服务之间的通讯、熔断、监控等全局的微服务治理框架。Spring Cloud 是不能离开Spring Boot这个环境的,Spring Cloud必须依赖于Spring Boot,但是Spring Boot可以离开Spring Cloud独立使用开发项目。 总结 1.各位读者如果有更好的见解可以留言,我们一起学习。 2.我们在不断的进步中,将会对Spring Boot系列进行深入的探究。 3.文章将会持续更新中。 推荐Spring Boot系列博客栈 Spring Boot系列]]></content>
<categories>
<category>后台</category>
<category>Spring Boot</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Spring Boot</tag>
</tags>
</entry>
</search>