Android之recyclerview

RecyclerView的使用


一、引用与布局

1、添加依赖

为了让RecyclerView兼容所有的版本,Android团队将RecyclerView定义在了support库中了。所以首先在build.gradle文件的dependencies中添加依赖:

1
compile 'com.android.support:recyclerview-v7:24.2.1'

2、添加布局

引用依赖之后,就可以在layer中添加该控件了,如下就完成了对recyclerview的添加:

1
2
3
4
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

二、数据的准备

1、模拟数据

以一个数组来模拟要展示的数据

1
2
3
4
5
6
private String[] data={
"Apple","Banana","Orange","Watermelon","Pear",
"Grape","Pineapple","Strawberry","Cherry","Mango",
"Apple","Banana","Orange","Watermelon","Pear",
"Grape","Pineapple","Strawberry","Cherry","Mango",
}

2、创建一个水果类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Fruit{
private String name;
private int imageId;
public Fruit(String name,int imageId){
this.name=name;
this.imageId=imageId;
}

public String getName(){
return name;
}

public int getImageId(){
return imageId;
}
}

3、创建一个item的布局文件

每一条数据以何种样式展示,加入是一张图片配一个名字。在layout文件夹下创建一个fruit_item.xml的布局文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<IextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
andriod:layout_marginLeft="10dp">
</LinearLayout>

三、适配器的编写

有了上面的准备就可以开始编写适配器了,需要继承自RecyclerView.Adapter类

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
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
private List<Fruit> fruitList;
static class ViewHolder extends RecyclerView.ViewHolder{
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View v){
super(v);
fruitImage=(ImageView)view.findViewById(R.id.fruit_image);
fruitName=(TextView)view.findViewById(R.id.fruit_name);
}
}

//构造函数接收数据源
public FruitAdapter(List<Fruit> fruitList){
this.fruitList=fruitList;
}

//通过架子啊fruit_item布局,创建ViewHolder实例,并返回该实例
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view=LayoutInflater.from(parent.getContext())
.inflate(R.layout.fruit_item,parent,false);
ViewHolder holder=new ViewHolder(view);
return holder;
}

//将RecyclerView的子项进行赋值
@Override
public void onBindViewHolder(ViewHolder holder,int position){
Fruit fruit=fruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fuit.getName());
}

//获取一共有多少项
@Override
public int getItemCount(){
return fruitList.size();
}
}

四、RecyclerView的加载

现在就可以展示数据了

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
public class MainActivity extends AppcompatActivity{
private List<Fruit> fruitList=new ArrayList<>();

@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//初始化数据
initFruits();
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);

//指定RecyclerView的布局方式
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter=new FruitAdapter(fruitList);

recyclerView.setAdapter(adapter);
}

//初始化数据
public void initFruits(){
Fruit apple=new Fruit("apple",R.drawable.apple_pic);
fruitList.add(apple);
//...

}
}

五、改变RecyclerView的布局

通过上边的步骤就可以成功展示列表数据了,排列方式是纵向排列。如果要实现横向布局怎么办呢?
第四部中出现了LinearLayoutManager,表示纵向布局,可以给这个类添加朝向,通过
setOrientation方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//初始化数据
initFruits();
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);

//指定RecyclerView的布局方式
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
//横向布局
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter=new FruitAdapter(fruitList);

recyclerView.setAdapter(adapter);
}

这需要改一下fruit_item.xml文件的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="100dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<IextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
andriod:layout_marginTop="10dp">
</LinearLayout>

这是就实现了横向布局。

六、其它的布局类

  • GridLayoutManager 网格状布局
  • StaggeredGridLayoutManager 瀑布流式布局

比如瀑布流式布局,只需修改layoutManager即可

1
2
//第一个参数表示列数;第二个参数指定布局的排列方向
StaggeredGridLayoutManager layoutManager=new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);

同样也需要修改fruit_item.xml文件,不再赘述

七、点击事件

RecyclerView的所有的点击事件都由具体的view去注册,这样就可以给整个子item注册事件,也可以给item中的子控件注册事件。是在Adaper中实现的

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
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
private List<Fruit> fruitList;
static class ViewHolder extends RecyclerView.ViewHolder{
//1
View fruitView;

ImageView fruitImage;
TextView fruitName;
public ViewHolder(View v){
super(v);

//2
fruitView=view;

fruitImage=(ImageView)view.findViewById(R.id.fruit_image);
fruitName=(TextView)view.findViewById(R.id.fruit_name);
}
}

//构造函数接收数据源
public FruitAdapter(List<Fruit> fruitList){
this.fruitList=fruitList;
}

//通过架子啊fruit_item布局,创建ViewHolder实例,并返回该实例
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view=LayoutInflater.from(parent.getContext())
.inflate(R.layout.fruit_item,parent,false);
//3
final ViewHolder holder=new ViewHolder(view);
//注册点击事件
holder.fruitView.setOnclickListener(new OnClickListener(){
@Override
public void onClick(View v){
int position=holder.getAdapterPosition();
Fruit fruit=fruitList.get(position);
//...
}
});

holder.fruitImage.setOnclickListener(new OnClickListener(){
@Override
public void onClick(View v){
int position=holder.getAdapterPosition();
Fruit fruit=fruitList.get(position);
//...
}
});

return holder;
}

//将RecyclerView的子项进行赋值
@Override
public void onBindViewHolder(ViewHolder holder,int position){
Fruit fruit=fruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fuit.getName());
}

//获取一共有多少项
@Override
public int getItemCount(){
return fruitList.size();
}
}

八、RecyclerView的更新

1、如果对数据源传入了新数据,需要更新RecyclerView的显示,可以通过调用adapter的NotifyItemInserted方法来实现

1
2
adatper.notufyItemInsert(msgList.size()-1);
recyclerView·scrollToPosition(msgList.size()-1);

Android之引入布局

一、引入xml文件

如果你要自定义标题栏,是不是要为每一个页面都重复的编写标题栏配置呢,当然不用,使用引入布局就可以实现一次编写处处使用的效果。
1、首先在layouy文件夹下新建title.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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bg">
<Button
android:id="@+id/title_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/back_bg"
android:text="back"
android:textColor="#fff"/>
<TextView
android:id="@+id/title_txt"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:garvity="center"
android:text="title"
android:textColor="#fff"
android:textSize="24sp"/>
<Button
android:id="@+id/title_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/edit_bg"
android:text="edit"
android:textColor="#fff"/>
</LinearLayout>

2、在Activity的布局文件中通过关键字include引入该布局

1
2
3
4
5
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/title" />
</LinearLayout>

3、在Activity的java文件中隐藏默认标题栏

1
2
3
4
5
6
7
8
9
10
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContent(R.layout.activity_main);

ActionBar actionBar=getSupportActionBar();
if(actionBar!=null){
actionBar.hide();
}
}

二、引入带监听事件的控件

仅仅引入xml文件,如果给title添加监听事件岂不是还要做大量的重复工作,对于一些通用的监听事件也可以独立出来。

1、新建一个类继承LinearLayout, 并且在里面添加监听按钮事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TitleLayout extends LinearLayout{
public TitleLayout(Context context , AttributeSet attrs){
super(context,attrs);
//inflate方法动态加载一个布局文件
LayoutInflater.from(context).inflate(R.layout.title,this);

//给返回按钮,添加监听
Button btn1=findViewById(R.id.title_back);
btn1.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
((Activity)getContext()).finish();
}
});
}
}

2、在activity_main.xml文件中添加这个控件即可

1
2
3
4
5
6
7
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.ui.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_contetn"/>
</LinearLayout>

Android之再谈Intent

1、在使用Intent启动新的activity时需要传递参数,如下面这样

1
2
3
4
5
6
Intent intent=new
Intent(MainActivity.this,SecondActivity.class);
String data="hello 你好!"
//第一个参数是键,第二个是值
intetn.putExtra(“extra_data”,data);
startActivity(intent);

但是对要传递的数据却不清楚,要么阅读SecondActivity代码,要么询问写SecondActivity的人,费时费力,如何减少这种麻烦呢?

2、可以在SecondActivity中添加一个静态方法

1
2
3
4
5
6
public static void actionStart(Context context,String data1,String data2){
Intent i=new Intent(context,SecondActivity.class);
i.putExtra("param1",data1);
i.putExtra("param2",data2);
context.startActivity(intent);
}

3、然后在MainActivity中可以直接调用SecondActivity中的方法来启动SecondActivity并进行传参

1
SecondActivity.actionStart(MainActivity.this,"data1","data2");

Android之退出应用

一、activity的退出

对于单个的activity的退出,只需要简单的调用finish()方法即可。

二、应用程序的退出

如果又多个activity启动,如何在任意一个activity中退出该应用程序呢?

思路:创建一个activity的管理类,管理所有启动的activity。
实现:
1、创建一个活动管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ActivityControllor{
public static List<Activity> activityList=new ArrayList<>();
public static addActivity(Activity activity){
activityList.add(activity);
}

public static void removeActivity(Activity activity){
activityList.remove(activity);
}

public static void finishAll(){
for(Activity activity:activityList){
if(!activity.isFinishing()){
activity.finish();
}
}
activityList.clear();
}
}

2、创建一个activity的基类,重写onCreateonDestroy方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BaseActivity extends AppCompatActivrty{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//添加此activity
Activitycontroller.addActivity(this);
}

@Override
public void onDestroy(){
super.onDestroy();
//移除该activity
ActivityController.removeActivity(this);
}
}

3、然后所有创建的Activity都继承该BaseActivity,若要退出,只需要调用静态的finishAll()方法即可,然后再杀掉当前进程

1
2
3
ActivityController.finishAll();
// killProcess()只能杀掉当前进程,mypid()方法获取当前进程
android.os.Process.killProcess(android.os.Process.myPid());

Android之启动模式

一、Android中启动模式的设置

1、可以在AndroidManifest.xml中给activity的标签指定android:launchMode属性来选择启动模式;

1
2
3
4
5
<activity
android:name=".FirstActivity"
android:launchMode="singleTask"
android:label="标题">
<activity>

2、也可以在代码上进行修改

二、不同启动模式的区别

  • standard:默认方式,每次启动都会创建新的实例;
  • singleTop:启动时如果栈顶就是,则直接调用栈顶的activity;
  • singleTask:每次启动时都检查栈内是否已存在,存在则将该activity之上的activity全部清除,然后调用该activity;
  • singleInstance:启用一个新的返回栈来管理这个活动。(可以解决共享实例问题)

Android之Intent

一、显式使用Intent

显式使用Intent很简单,如下所示就可以从MainActivity中启动secondActivity

1
2
3
4
//第一个参数表示上下文,第二个参数表示目标活动
Intent intent=new
Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);

二、隐式使用Intent

隐式即是 不直接通过activity的类名来启动activity。怎么做呢?
1、在AndroidManifest.xml中找到要启动的Activity,添加intent-filter

1
2
3
4
5
6
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>

2、只有在同时匹配上Intent时,才能启用该活动,在创建Intent时直接将action的字符串com.example.activitytest.ACTION_START传递进去,表示想要启动过能够响应这个字符串的Activity,下面的代码没有添加category,是因为android.intent.category.DEFAULT是默认的category,在调用startActivity时会自动添加到Intent中。

1
2
3
Intent intent=new
Intent(“com.example.activitytest.ACTION_START”);
startActivity(intent);

3、自定义category
如果要启动的category有自己的category,怎么办呢,如下

1
2
3
4
5
6
7
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.example.activitytest.My_CATEGORY"/>
</intent-filter>
</activity>

使用Intent的addCategory方法,就可以启动指定的activity了

1
2
3
4
Intent intent=new
Intent(“com.example.activitytest.ACTION_START”);
intent.addCategory("com.example.activitytest.My_CATEGORY");
startActivity(intent);

注意:没个intent只能指定一个action,但是可以指定多个category

三、隐式启用系统的服务

如何隐式的调用系统浏览器呢,如下:

1
2
3
4
Intent intent=new
Intent(Intent.ACTION_VIEW);
intent.setData("http://www.baidu.com");
startActivity(intent);

其中actionIntent.ACTION_VIEW是一个系统内置的动作,常量值为“android.intent.action.VIEW”;

‘setData’方法指定当前Intent正在操作的数据,通常以字符串的形式传如到Uri.parse()方法中解析产生。

可以给<intent-filter/>标签配置<data>标签,更精确的指定当前活动能够响应什么类型的数据,<data>标签可以配置如下内容:

  • anroid:scheme 指定数据的协议
  • android:host 指定数据的主机名,
  • android:port 指定数据的端口
  • android:path 指定主机鸣鹤端口之后的部分
  • android:mimetype 指定处理的数据类型

只有<data>标签指定的内容和intent中携带的Data一致时,当前活动才能响应该intent。不过一般<data>标签不会指定过多的内容,如要启动浏览器,只需要指定Android:acheme为http即可,
如,给一个activity指定:

1
2
3
4
5
6
7
<activity android:name=".ThirdActivity">
<intent-filter>
<action android:name="android.intent.action.View"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"/>
</intent-filter>
</activity>

这是如过再隐式调用浏览器服务,就会弹出让你选择的项。

除了调用系统的浏览器,还可以调用系统的一系列的其它服务,如调用系统的电话拨号界面:

1
2
3
4
Intent intent=new
Intent(Intent.ACTION_DIAL);
intent.setData("tel:10086");
startActivity(intent);

四、向下一个Activity传递数据

1、启动新的activity时传递数据

1
2
3
4
5
6
7
//第一个参数表示上下文,第二个参数表示目标活动
Intent intent=new
Intent(MainActivity.this,SecondActivity.class);
String data="hello 你好!"
//第一个参数是键,第二个是值
intetn.putExtra(“extra_data”,data);
startActivity(intent);

2、然后就能在SecondActivity的create方法中获取了

1
2
3
Intent intent=getIntent();
//又键获取相应的值
String data=intent.getStringExtra("extra_data");
  • 这里如果值是字符串就使用getStringExtra;
  • 如果是整型数据,就使用getIntExtra;
  • 如果是布尔型数据,就使用getBooleanExtra;

当然也可以传递bundle对象。

五、向上一个Activity返回数据

如何给上一个activity中回传一个数据呢?
1、在第一个activity中使用startActivityForResult方法;

1
2
3
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
//1是请求码
startActivityForResult(intent,1);

然后重写onActivityResult方法,三个参数,第一个就是启动时传入的请求码,第二个是返回数据时传入的处理结果,第三个是携带着返回数据的Intent:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){
switch(requestCode){
case:1
if(resultCode==RESULT_OK){
String requestData=data.getStringExtra("data_return");
//...
}
break;
default:
}
}

2、在要请求的SecondActivity中相应位置,组装intent,然后调用finish()销毁当前activity即可:

1
2
3
4
5
6
7
8
9
btn.setOnClickListener(new OnCickListener(){
@Override
public void onClick(View v){
Intent i=new Intent();
i.putExtra("data_return","我是数据!");
setResult(RESULT_OK,intent);
finish();
}
});

这时,点击相应按钮,就可以在FirstActivity中收到回传的数据了。

如果SecondActivity页面是通过按返回键销毁的呢,只需要重写onBackPressed方法即可:

1
2
3
4
5
6
7
@Override
public void onBackPressed(){
Intent i=new Intent();
i.putExtra("data_return","我是数据!");
setResult(RESULT_OK,intent);
finish();
}

Gradle入门

一、构建工具

1、构建工具的作用

  • 依赖管理
  • 测试、打包、发布
  • 机器能干的活,绝不自己动手
    *

2、主流构建工具

  • Ant:编译、测试、打包
  • Maven: 依赖管理、发布
  • Gradle:

3、Gradle是什么
一个开源的 项目自动化构架工具 ,建立在Apache Ant和Apache Maven概念的基础之上,并且引入基于Groovy的特定领域语言(DSL),而不再使用XML形式管理构建脚本。

二、Gradle的安装

  1. 确保本地已经安装JDK
  2. 从Gradle官网下载Gradle
  3. 配置环境变量:GRADLE_HOME,并且添加到path,%GRADLE_HOME%\bin
  4. 验证是是否安装成功,gradle -v

三、Groovy是什么

Groovy是用于java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该语言不必编写过多的代码,同时又具有闭包和动态语言中的其它特性。

3.1 与java比较
  • Groovy完全兼容java语法
  • 分号可选
  • 类、方法默认是public
  • 编译器给属性自动添加getter/setter方法
  • 属性可以直接用点号获取
  • 最后一个表达式的汁会被作为返回值(也就是return字符串可以不写)
  • == 等同于equals(),不会有NullPointerExceptioins
3.2 高效的grovvy特性
  • 可选类型定义
1
def version=1
  • assert语句
1
assert version==2
  • 可选的括号
1
2
3
//有没有括号,效果是一样的
println(version) // 1
println version // 1
  • 字符串(单引号、双引号、三引号)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//单引号就是字符串
def s1='com'

//双引号可以插入字符串,${}
def s2="afei"
def s22="afei is a ${version}"

//三引号可以换行
def s3=''' wo shi ni die'''

def s32='''wo
shi
ni
ma
'''
  • 集合API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//list
def buildTools=['ant','maven']
buildTools<< 'gradle'
assert buildTools.getClass()== ArrayList
assert buildTools.size() == 3

//map
def buidYears=['ant':2000,'maven':2004]
buildYears.gradle=2009

println buildYears.ant //2000
printlb=n buildYears['gradle'] //2009

println buildYears.getClass()
  • 闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def c1={
v ->
println v
}

def c2={
println 'hello'
}

def method1(Closure closure){
closure('param');
}

def method2(Closure closure){
closure();
}

method1(c1) //param
method(c2) //hello
3.3 grovvy基础知识
1
2
3
4
5
6
7
8
9
10
11
12
13
//构建脚本默认都有个Project实例

apply plugn : 'java' //命名参数

version='0.1'

repositories{
mavenCentral()
}

dependencies{
compile 'common-codec:commons-codec:1.6'
}

四、Gradle项目

4.1 Gradle目录结构
  • src
  • main
  • java
  • resources 配置文件
  • webapp web资源
  • test
  • java
  • resources

五、构建脚本

项目 project

一个项目代表一个正在构建的组件(比如一个jar文件),当构建启动后,Gradle会基于build.gradle实例化一个org.gradle.api.Project类,并且能够通过project变量使其隐式可用。

  • group 、 name 、 version
  • apply(应用一个插件)、 dependencies(用来声明依赖,)、 repositories(声明仓库,去哪个仓库找jar包)、 task(声明项目中有哪些任务)
  • 属性等其它配置方式:ext、gradle.properties(键值对的方式声明属性)
任务 task

任务对应org.gradle.api.Task。主要包括任务动作和任务依赖。任务动作定义了一个最小的工作单元。可以定义依赖于其它任务、动作序列和执行条件。

  • dependsOn
  • doFirst(在动作列表最前边添加动作)、doLast(在动作列表最后边添加动作) <<
自定义一个task
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def cresteDir={
path ->
File dir=new File(path);
if(!dir.exists()){
dir.mkdirs();
}

}

task makeJavaDir(){
def paths=['src/main/java','src/main/resources','src/test/java','src/rest/resources'];
doFirst{ //动作代码,执行阶段会执行
path.forEach(createDir);
}
}

task makeWebDir(){
dependsOn 'makeJavaDir' //声明依赖makeJavaDir,执行makeWebDir时先执行makeJavaDir
def paths=['src/main/webapp','src/test/webapp'];
doLast{//动作代码,执行阶段会执行
paths.forEach(createDir);
}
}

依赖管理

概述

几乎所有的基于JVM的软件项目都需要依赖外部类库来重用现有的功能。自动化的依赖管理可以明确依赖的版本,可以解决因传递性依赖带来的版本冲突。

工件坐标

group、name、version

常用仓库
  • mavenLocal(本地仓库)/mavenCentral(公网仓库)/jcenter(公网仓库)
  • 自定义的maven仓库
  • 文件仓库
依赖的传递性
  • B依赖A,如果C依赖B,那么C依赖A
依赖阶段配置
  • compile、runtime
  • testCompile、testRuntime

配置maven仓库地址

1
2
3
4
5
6
7
respositories{
mavenLocal() //配置本地仓库
mavenCentral() //配置公网仓库
maven{ //配置私服仓库
url '私服仓库地址'
}
}
解决版本冲突

传递依赖可能产生版本冲突

解决版本冲突

  • 产看依赖报告
    • 修改默认解决策略(gradle默认使用最新的版本)
      1
      2
      3
      4
      5
      configurations.all{
      resolutionStrategy{
      failOnVersionConflict() //强制冲突报错
      }
      }
  • 排除传递性依赖

    • 修改默认解决策略
      1
      2
      3
      4
      compile('org.hibernate:hibernate-core:3.6.3.Final'){
      exclude group:"org.sef4j",module:"slf4j-api" //module就是坐标中的name属性
      //transitive=false //排除所有的传递性依赖
      }
  • 强制一个版本

  • 1
    2
    3
    4
    configurations.all{
    resolutionStrategy{
    force 'org.sef4j:slf4j-api:1.7.24' }
    }

多项目构建

项目模块化

settings.gradle就是用来多项目构建的

1
2
3
4
rootProject.name = 'todo'
include 'respository'
include 'model'
include 'web'

如果每个module都配置相同的东西,可以将它们删除,然后在根build.gradle文件中添加:
1
2
3
4
allprojects{
apply plugn: 'java'
sourceCompactibility=1.8
}
如果需要将某个模块大包成war包,需要在改模块下面的build.gradle下添加
1
apply plugn: 'war'
如果要依赖另一个modele
1
2
3
dependencies {
compile project(": repository")
}
在根build.gradle文件中给子module添加依赖
1
2
3
4
5
6
7
8
9
10
subprojects {
respositories{
mavenCentral()
}

dependencies{
compile 'ch.qos.logback:logback-class^^^'
testCompile group: 'junit', name: 'jnuit'
}
}
如果要版本统一

可以先将子模块中的版本号都取消,然后新建一个gradle.properites,在里面写入要添加的版本号:

1
2
group = ‘com.afei.gradle’
version=1.0

自动化测试

测试配置

1
2
3
dependencies{
testCompile 'junit:junit:4.11'
}
测试发现
  • 任何即成自junit.framework.TestCase或groovy.util.GroovyTestCase
  • 任何使用@RunWith注解的类
  • 任何至少包含一个被@Test注解的类

发布

gradle没有自己的仓库,一般发布到maven仓库。
gradle使用插件发布,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apply plugn:'maven-pulish'
publishing{
publications{
myPublish(MavenPulication){
from components.java
}
}

repositories{
maven{
name "myrepo"
url "私服地址"
}
}
}

Java反射

1、Class类

java语言中万物皆对象,(基础类型有包装类,静态成员除外)。


类也是对象,类是谁的对象呢?–java.lang.Class的实例对象

eg:

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
public class ClassDemo(){
public static void main(String[] args){
//Foo的实例对象如何表示
Foo foo=new Foo();

//Foo这个类也是一个实例对象,Class类的实例对象,如何表示呢
//任何一个类都是Class的实例对象,这个实例对象有三种表示方式

//第一种表示方式-->任何一个类都有一个隐含的静态成员变量
Class c1=Foo.class;

//第二种表达方式,已经知道的该类的对象通过getClass方法
Class c2=foo1.getClass();

/*官网c1,c2表示了Foo类的类类型(class type)
*万物皆对象,
*类也是对象
*这个对象我们成为该类的类类型
*/

//不管c1,c2都代表了Foo类的类类型,一个类只可能是Class类的一个实例对象
System.out.println(c1==c2);

//第三种表达方式
Class c3=null;
try{
c3=Class.forName("com.afei.reflect.Foo");
}catch(ClassNotFoundException e){
e.printStaticTrace();
}
System.out.println(c3==c2);

//完全可以通过类的类类型创建该类的对象实例,-->通过c1,c2,c3创建Foo的实例
try{
Foo foo=c1.newInstance();//需要有无参数的构造方法
foo.print();
}catch(InstantictionException e){
e.printStaticTrace();
}catch(IllegalAccessException e){
e.printStaticTrace();
}



}
}

class Fool{
void print(){
System.out.println("Foo");
}
}

动态加载类

Class.forName(“类的全称”);

  • 不仅表示了类的类类型,还代表了动态加载类
  • 请区分编译,运行
  • 编译时刻加载类是静态加载类,运行时刻加载类hi动态加载类
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
class Office{
public static void main(String[] args){

//new 创建的对象,是静态加载类,在编译时刻就需要加载所有的可能使用到的类
//通过动态加载类可以解决该问题
if("World".equals(args[0])){
World w=new World();
w.start();
}

if("Excell".equals(args[0])){
Excell e=new Excell();
e.start();
}

}
}

class World{
void start(){
System.out.println("我是world!");
}
}

class Excell{}
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
class OfficeBetter{
public static void main(String[] args){

try{
//动态加载类,在运行时刻加载
Class c=ClassforName(args[0]);

OfficeAble oa=(OfficeAble)c.newInstance();
c.start();
}catch(Exception e){
e.printStaticTrace();
}
}
}


interface OfficeAble{
void start();
}
class World implements OfficeAble{
public void start(){
System.out.println("我是world!");
}
}

class Excell implements OfficeAble{
public void start(){
System.out.println("我是Excell!");
}
}

可以使用 java OfficeBetter Excell 研验证

Java 获取方法信息

基本的数据类型

  • void关键字 都存在类类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ClassDemo2(){
public static void main(String[] args){
//
Class c1=int.class;//int的类类型
Class c2=String.class;//String类的类类型,String类字节码

Class c3=double.class;//
Class c4=Double,class;//
Class c5=void.class;//


//返回不包含包名的类的名称
System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c3.getName());


}
}

Class类的基本操作

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
/**
* 打印类的信息,包括类的成员函数,成员变量
*/
public class ClassUtil(){
/**
* 打印成员函数
*/
public static void printClassMessage(Object obj){
//获取类的信息,首先要获取类的类类型

Class c=obj.getClass();//传递的是哪个字类的对象,c就是改字类的类类型

//获取类的名称
System.out.println("类的名称是:"+c.getName());

/**
* Method类,方法和对象
* 一个成员方法就是一个Method对象
* getMethods()方法获取的是所有public的函数
* getDeclareMethods()获取的是该类自己声明的方法,不问访问权限
*/
Method[] ms=c.getMethods();//c.getDeclareMethos();

for(int i=0,int len=ms.length;i<len;i++){
//得到方法的返回值类型-->返回值类型的类类型
Class returnType=ms[i].getReturnType();
System.out.print(returnType.getName()+" ");

//得到方法的名称
System.out.print(ms[i].getName()+" ( ");

//得到参数类型-->得到的是参数列表的类型的类类型
Class[] paramTypes=ms[i].getParameterType();

//
for(Class class1:paramTypes){
System.out.print(class1.getName()+" , ");
}

System.out.println(" ) ");

}


/**
* 获取字段的函数
*/
public static void getFieldMessage(Object obj){
Class c=obj.getClass();

/**
* 成员变量也是对象
* java.lang.reflect.Field
* Field类封装了关于成员变量的操作
* getFields()获取的是所有的public的成员变量的信息
* getDeclaredFields()获取所有该类自己声明的成员变量(可以是公有,也可以是私有)
*/
//Field[] fs=c.getFields();
Field[] fs=c.getDeclaredFields();
for(Field field:fs){
//得到成员变量的类型的类类型
Class fieldType=field.getType();
String typeName=fieldType.getName();

//得到成员变量的名称
String fieldName=field.getName();
System.out.println(typeName+" "+ fieldName);
}
}

/**
* 获取构造函数
*/
public static void getConMessage(Object obj){
Class c=obj.getClass();
/**
*构造函数也是对象
*java.lang.Constructor中封装了构造函数的信息
*getConstructor获取所有的public的构造函数
*getDeclareConstrutor获取所有的构造函数
*/
//Constructor[] cs=c.getConstructor();
Constructor[] cs=c.getDeclareConstructor();
for(Constructor constructor: cs){
System.out.print(constructor.getName+"(");

//获取构造函数的参数列表-->得到的是参数列表的类类型
Class[] paramTypes=constructor.getParameterTypes();
for(Class class1:paramTypes){
System.out.print(class1.getName+",");
}
System.out.print(")");

}

}

}

方法反射的基本操作

如何获取某个方法:方法的名称和方法的参数列表才能为宜决定某了列表


方法的反射操作:method.invoke(对象,参数列表)

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
public class MethodDemo1{
public static void main(String[] args){
//获取print(int , int)方法,1、要获取一个方法就是获取类的信息,获取类的信息首先要获取类的类类型
A a1=new A();
Class c=a1.getClass();
/**
*2 获取方法,名称和参数列表来决定
* getMethod获取的是public的方法
* getDelcaredMethod自己声明的方法
*/
try{
//Method m=c.getMethod("print",new Class[]{int.class,int.class});
Method m=c.getMethod("print",int.class,int.class);


//方法的反射操作;
//a1.print(10,20); 方法的反射操作时用m对象来进行方法调用,和a1.print效果相同
//方法如果没有返回值返回null,有返回值返回具体的返回值
//Object obj=m.invoke(a1,new Object[]{10,20});
Object obj=m.invoke(a1,10,20);

//获取方法print(String,String)
Method m1=c.getMethod("print",String.class,String.class);
//用方法进行反射操作
o=m1.invoke(a1,"hello","world");
}catch(Exception e){
e.printStaticTrace();
}


//获取没有参数的方法
//Method m2=c.getMethod("print",new Class[]{});
Method m2=c.getMethod("print");
//m2.invoke(a1,new Object[]{});
m2.invoke(a1);

}
}

class A{
public void print(){
System.out.println("我没有参数");
}

public void print(int a,int b){
System.out.println(a+b);
}
public void print(String a,String b){
System.out.println(a.toUpperCase()+","+b.toLowerCase());
}
}

通过反射了解集合泛型的本质

  • 通过Class, Method来认识泛型的本质
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
public class MethodDemo4{
public static void main(String[] args){
ArrayList list=new ArrayList();//没有泛型,可以放任何数据;

ArrayList<String> list1=new ArrayList<String>();//泛型指定只能放指定的类型
list1.add("hello");
//list1.add(20);//错误


Class c1=list.getClass();
Class c2=list.getClass();
System.out.println(c1==c2);//true
//反射的操作都是编译之后的操作

/**
*c1==c2结果返回true,说明编译之后的集合的泛型时去泛型化的
* java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了
* 验证:通过反射操作,绕过编译
*/
try{
Method m=c2.getClass("add",Object.class);
m.invoke(list1,100);//绕过编译操作就绕过了泛型
System.out.println(list1.size());
System.out.println(list1);

/*for(String string:list1){
System.out.println(string);
}
}catch(Exception e){
e.printStaticTrace();
}*///现在不能这样遍历,因为100不能转化为String


}
}

SpringBoot进阶

内容

  • 1、使用@Valid表单验证
  • 2、使用AOP处理请求
  • 3、统一一场处理
  • 4、单元测试

一、表单验证

首先说一下git的使用,将项目clone下来之后,使用git check -b web-2 web-2就可以在本地使用分枝web-2了。
当参数过多的时候,在函数里面的参数很多就显得很业余,如果还要往里面添加属性,怎么办呢?可以将单个的属性参数换作一个类,这样的话就可以不更新函数了。
比如girlAdd方法原来是这样的,

1
2
3
4
5
6
7
8
9
10
11

@PostMapping(value = "/girls")
public Girl girlAdd(@RequestParam("cupSize") String cupSize,
@RequestParam("age") Integer age){

Girl girl=new Girl();
girl.setCupSize(cupSize);
girl.setAge(age);

return girlRepository.save(girl);
}

可以改为

1
2
3
4
5
6
7
8
9
@PostMapping(value = "/girls")
public Girl girlAdd(Girl g){

Girl girl=new Girl();
girl.setCupSize(g.getCupSize());
girl.setAge(g.getAge());

return girlRepository.save(girl);
}

如果想让未满18岁的少女插入不成功,怎么做呢,首先进入Girl类内部,在age的属性上添加注解@Min,value写入最小年龄,message写入错误信息,如下

1
2
@Min(value = 18, message = "未成年少女禁止入内")
private Integer age;

然后在GirlController中的函数girlAdd参数钱加上注解@Valid即可,还要考虑到如何获取验证结果呢,这是虎需要用到BindingResult添加到参数重,如下

1
2
3
4
5
6
7
8
9
10
11
12
@PostMapping(value = "/girls")
public Girl girlAdd(@Valid Girl g , BindingResult bindingResult){
if (bindingResult.hasErrors()){
System.out.println(bindingResult.getFieldError().getDefaultMessage());
}

Girl girl=new Girl();
girl.setCupSize(g.getCupSize());
girl.setAge(g.getAge());

return girlRepository.save(girl);
}

总结:表单验证,首先在类(Girl)中添加@Min注解,设置阈值和错误信息;在Controller的类(GirlController)中相应函数(girlAdd)的参数用@Valid注解响应的参数类(Girl g);并且用BindingResult来获取验证信息。

二、AOP统一处理请求日志

  • AOP是一种编程范式
    • 与语言无关,是一种程序设计思想
    • 面向切面(AOP)Aspect Oriented Programming
    • 面向对象(OOP)Object Oriented Programming
    • 面向过程(POP)Procedure Oriented Programming

AOP将通用逻辑从业务逻辑中分离出来。

2.1

在获取请求之前判断用户是否已登陆,难道需要在每一个方法中都做判断吗?不需要。使用aop就可以解决。
首先在porm中添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2

然后新建一个类HttpAspect添加注解@Aspect,并且添加@Component将这个类引入到spring容器中去。然后在里面使用@Before注解,规定要拦截的函数,这样就可以在每次调用这个函数之前都做响应处理了。如果是想对所有的函数度做拦截,可以将@Before中的函数换作*;如果想让函数执行之后再调用某个方法,可以使用@After注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect
@Component
public class HttpAspect {

/**
* 拦截girlList方法
* girllist只要是这个方法,不论是任何参数都会被拦截。
* 在girlList放置执行之前被拦截
*
* 如果想拦截所有的方法,只需要吧girlList换为*即可
*/
@Before("execution(public * com.afei.controller.GirlController.girlList())")
public void log(){
System.out.println("1111111111");
}

/**
* 同理这个方法,会在拦截函数执行之后触发
*/
@After("execution(public * com.afei.controller.GirlController.girlList())")
public void doAfter(){
System.out.println("222222222");
}
}

2.3

可是这时候再@Before和@After之中的拦截函数有重复,这时候可以做以下改进:

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
@Aspect
@Component
public class HttpAspect {

@Pointcut("execution(public * com.afei.controller.GirlController.girlList())")
public void log(){
}

/**
* 拦截girlList方法
* girllist只要是这个方法,不论是任何参数都会被拦截。
* 在girlList放置执行之前被拦截
*
* 如果想拦截所有的方法,只需要吧girlList换为*即可
*/
@Before("execution(public * com.afei.controller.GirlController.girlList())")
public void doBefore(){
System.out.println("1111111111");
}

/**
* 同理这个方法,会在拦截函数执行之后触发
*/
@After("execution(public * com.afei.controller.GirlController.girlList())")
public void doAfter(){
System.out.println("222222222");
}
}

2.4

通常使用System.out.println()方法不如使用日志打印,可以使用private final static Logger logger= LoggerFactory.getLogger(HttpAspect.class);来替代,只需要在相应的地方打印信息logger.info("222222222"));即可

2.5 如何获取方法的http请求参数呢

只需要在HttpAspect类中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Before("log()")
public void doBefore(JoinPoint joinPoint){
//System.out.println("1111111111");
//logger.info("1111111111");
ServletRequestAttributes attributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attributes.getRequest();

//获取http请求的内容

logger.info("url={}",request.getRequestURI());

logger.info("method={}",request.getMethod());

logger.info("ip={}",request.getRemoteAddr());

logger.info("class_method={}",joinPoint.getSignature().getDeclaringTypeName());

logger.info("args={}",joinPoint.getArgs());
}

如何获取http请求的结果呢,需要使用到@AfterReturning的注解,

1
2
3
4
//获取请求返回的结果 @AfterReturning(returning="object" ,pointcut = "log()")
public void doAfterReturning(Object object){
logger.info("response={}",object);
}

三、统一异常处理

需求:
获取某个女生的年龄并判断
小于10,返回“应该上小学”
大于10且小于16,返回“可能在上高中”

如何在有异常发生时,按照指定的数据格式处理回应呢

3.1 异常的捕获处理:

首先新建一个类ExceptionHandle用@ControllerAdvice注解,在后在里面声明一个方法handler并且用@ExceptionHandler注解,value值为Exception.class,和@ReponseBody注解。

1
2
3
4
5
6
7
 @ControllerAdvice
public class ExceptionHandle {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result handler(Exception e){
return ResultUtil.error(100,e.getMessage());
}

然后在一些位置抛异常,比如girlService中的getAge方法

1
2
3
4
5
6
7
8
9
10
11
public void getAge(Integer id) throws Exception{
Girl girl=girlRepository.findOne(id);
Integer age=girl.getAge();
if (age<10){
//
throw new Exception("你还在上小学吧");
}else if(age>10 && age<16){
throw new Exception("你可能上初中");
}

}

这样抛的异常就全部交给了ExceptionHandle来处理,此时用户再传入相应的参数的就可以按照指定的Result的方式来输出了。

3.2 自定义异常

如果我想你上小学的时候返回100,上初中的时候返回101,但是抛异常时又没有自定义错误码的方法,这时就需要我们自定义异常类了。
新建一个GirlException的类继承自RuntimeException。spring只对RuntimeException进行回滚,对Exception是不会进行回滚的。在GirlException中增加错误码的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 public class GirlException extends RuntimeException{

private Integer code;
public GirlException(Integer code,String message){
super(message);
this.code=code;
}

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}
}

然后就可以在GirlService中抛异常时使用自定义的异常了:

1
2
3
4
5
6
7
8
9
10
11
public void getAge(Integer id) throws Exception{
Girl girl=girlRepository.findOne(id);
Integer age=girl.getAge();
if (age<10){
//
throw new GirlException(100,"你还在上小学吧");
}else if(age>10 && age<16){
throw new GirlException(101,"你可能上初中");
}

}

最后在ExceptionHandle类中的handler方法中对GirlException进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
 @ControllerAdvice
public class ExceptionHandle {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result handler(Exception e){

if (e instanceof GirlException){
GirlException girlException=(GirlException) e;
return ResultUtil.error(girlException.getCode(),girlException.getMessage());
}
return ResultUtil.error(100,e.getMessage());
}
}

此时传入相应的请求就会安规定返回相应的数据了.
要过要抛出系统异常,但是如何跟踪异常呢,这时需要使用日志工具了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 @ControllerAdvice
public class ExceptionHandle {
private final static Logger logger= LoggerFactory.getLogger(Exception.class);

@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result handler(Exception e){

if (e instanceof GirlException){
GirlException girlException=(GirlException) e;
return ResultUtil.error(girlException.getCode(),girlException.getMessage());
}else{
logger.error("[系统异常]{}",e);
return ResultUtil.error(-1,"未知错误");
}

}
}

这样发生系统异常时,就可以在控制台打印消息了

额外的错误码管理

此时异常捕获已经差不多,有个小地方可以优化,就是错误码管理,可以将code和msg用枚举进行管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public enum ResultEnum {

UNKONW_ERROR(-1,"未知错误"),
SUCCESS(0,"成功"),
PROMARY_SCHOOL(100,"你在上小学吧"),
MIDDLE_SCHOOL(101,"你可能在上初中");


private Integer code;
private String msg;

ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

public Integer getCode() {
return code;
}

public String getMsg() {
return msg;
}
}

然后在GirlService中修改相应的异常:

1
2
3
4
5
6
7
8
9
10
11
public void getAge(Integer id) throws Exception{
Girl girl=girlRepository.findOne(id);
Integer age=girl.getAge();
if (age<10){
//
throw new GirlException(ResultEnum.PROMARY_SCHOOL);
}else if(age>10 && age<16){
throw new GirlException(ResultEnum.MIDDLE_SCHOOL);
}

}

此时会有错误,将GirlException中的构造函数进行改造:

1
2
3
4
public GirlException(ResultEnum resultEnum){
super(resultEnum.getMsg());
this.code=resultEnum.getCode();
}

四、单元测试

首先对要测试的类(如GirlService)中添加一个可以用的方法,

1
………………

然后在test目录下床及亲一个测试类用@RunWith(SpringRunner.class)注解,还需要加上@SpringBootTest的注解.然后使用@Autowired注入GirlService,就可以写测试函数了

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringRunner.class)
@SpringBootTest
public class GirlServiceTest{
@Autowired
private GirlService girlService;

@Test
public void findOneTest(){
Girl girl=girlService.findOne(73);
Assert.assertEquals(new Integer(13),girl.getAge());
}
}

也可以使用ide自动生成,gotoTest,但是还需要自己添加相应的注解

如何给api进行测试呢,希望想postman一样使用url测试,这时需要给类额外用到@AutoConfigureMockTest注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockTest
public class GirlControllerTest{
@Autowired
private MockMvc mvc;

@Test
public void girlList() throws Exception{
//get 使用get方法,post使用post方法
mvc.perform(MockMvcRequestBuilders.get("/girls")).andExcept(MockMvcResultMatchers.status().isOk())
.andExcept(MockMvcResultMatchers.content().string("abc"));//判断内容
}
}

SpringBoot基础

本笔记是慕课网视频记录的http://www.imooc.com/learn/767

一、 创建页面

常见一个Spring Boot工程,然后在里面创建一个类用@RestController标示;然后创建一个方法用@RequestMapping(value = "hello" , method = RequestMethod.GET),其中value表示延长路径,method表示请求方法。
如下

1
2
3
4
5
6
7
@RestController
public class HelloController {
@RequestMapping(value = "hello" , method = RequestMethod.GET)
public String say(){
return "Hello Spring Boot,我是你爹";
}
}

二、启动方式。

  • 第一种是直接通过ide进行启动;
  • 第二种是进入项目所在目录输入mvn spring-boot:run;
  • 第三种启动方式,使用maven先编译mvn install,然后进入target目录下,会发现又一个.jar 的文件,然后编译这个jar文件即可`java-jar .jar`.
  • 就可以通过localhost:8080/girl访问了;

三、属性配置

1
2
3
4
spring.datssource.url:jdbc:mysql://127.0.0.1:3306
spring.datssource.username:root
spring.password:123456
spring.datasource.driver-class-name:com.mysql.jdbc
3.1 改变端口号,增加路径

在‘application.properities’文件中添加

1
2
server.port=8081
server.context-path=/girl

此时访问端口就变成了8081,并且路径前添加了girl,这时候就要通过如下地址才能访问刚才的页面localhost:8081/girl/hello
推荐使用yml文件代替原来的properities

3.2 将配置文件中的属性,引到变量中去

在application.yml中添加

1
cupSize: B

然后在HelloController文件中添加变量

1
2
@Value("${cupSize}")
private String cupSize;

这样配置文件中的cupSize就引入变量中了

也可以在配置文件中,使用变量

1
2
3
4
5
6
server:
port: 8085
context-path: /girl
cupSize: B
age: 18
content: "罩杯是: ${cupSize} , 年龄是:${age} 。"

需要说明的是,在配置文件中不需要说明类型,只需要在变量处给出声明就可以;

3.3 使用分类来代替配置文件

如果属性较多,在配置文件中一个一个写会过于繁琐,这时候需要创建一个类GirlProperties,获取前缀是girl的配置,同时需要在类GirlProperties上添加@Component的注解.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
@ConfigurationProperties(prefix = "girl")
public class GirlProperties {

private String cupSize;
private Integer age;

public String getCupSize() {
return cupSize;
}

public void setCupSize(String cupSize) {
this.cupSize = cupSize;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

然后在application.yml中添加

1
2
3
girl:
cupSize: D
age: 28

如何使用呢,在HelloController中添加@Autowired,就可以使用了

1
2
3
4
5
6
7
8
@Autowired
private GirlProperties girlProperties;

@RequestMapping(value = "hello" , method = RequestMethod.GET)
public String say(){
//return cupSize;
return girlProperties.getCupSize();
}

但是有多个环境,比如开发环境和生产环境配置不同的时候,频繁的修改application.yml配置文件吗?不要咬的,我们可以创建一个application-dev.yml在里面放置开发环境的配置,然后再创建一个application-prod.yml的文件,在里面放置生产环境的配置。最后只需要在application.yml文件中作选择即可,比较选择开发环境,只需写入:

1
2
3
spring:
profiles:
active: dev

而如果使用命令行的方式启动,需要:

1
java -jar ****.jar --spring.profiles.active=dev

四、Controller的使用

注解 描述
@Controller 处理http请求
@RestController Spring4之后新加的注解,原来返回json需要@ResponseBody配合@Controller
@RequestMapping 配合url映射
4.1 @Controller和@RestController的区别

将项目中的HelloController的注解@RestController换做@Controller,再次运行就会报错,这时候需要添加模版,首先在porm中添加注解

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

然后再resource目录下新建目录templates,并新建文件indexhtml,随意写入东西。然后在HelloController中写入:

1
2
3
4
@RequestMapping(value = "hello" , method = RequestMethod.GET)
public String say(){
return "index.html";
}

这时候就可以访问这个借口了。虽然@Controller配合模版可以实现与@RestController同样的效果,但是使用模版会带来性能上的损耗,不推荐使用。

4.2 如何做到两个借口是同一个方法

使用@RequestMapping的注解,在value中使用集合即可:

1
2
3
4
5
@RequestMapping(value = {"hello","hi" }, method = RequestMethod.GET)
public String say(){
//return cupSize;
return girlProperties.getCupSize();
}

这样的就既可以使用ip:port/path/hello,又可以ip:port/path/hi来访问相同的请求了。
也可以给整个类设定一个url,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping(value = "/hello")
public class HelloController {

@Autowired
private GirlProperties girlProperties;


@RequestMapping(value = {"/say"}, method = RequestMethod.GET)
public String say(){
//return cupSize;
return girlProperties.getCupSize();
}

}

这样就可以使用ip:port/path/hello/say来访问girl的属性了。

@RequestMapping(value = {"hello","hi" }, method = RequestMethod.GET)中的请求方式可以是GET也可以是POST,如果不写的话,两种都可以,但是并不推荐这么做。

4.3 如何处理url中的参数
注解 描述
@PathVariable 获取url中的数据
@RequestParam 获取请求参数的值
@GetMapping 组合注解

4.3.1 不带等号的传餐方式

如何使用@PathVariable传递参数呢,在@RequestMapping的value中添加参数用大括号括起来,然后在具体函数的传入参数用@PathVarable来声明,如下

1
2
3
4
@RequestMapping(value = {"/say/{id}"}, method = RequestMethod.GET)
public String say(@PathVariable("id") Integer id){
return "id="+id;
}

这样就可以通过ip:port/path/hello/say/36来传递id=36了。当然在@RequestMapping中的”{id}”可以写在任何路径。

4.3.2 带等号的传参方式

但是如何获取ip:port/path/hello/say/id=100这种方式中的id的值的,使用@RequestParam,修改如下:

1
2
3
4
@RequestMapping(value = {"/say"}, method = RequestMethod.GET)
public String say(@RequestParam("id") Integer id){
return "id="+id;
}

如何设置要传递的参数是否必传,以及如何设定默认值呢,只需要使用required和defaultValue在RequestMapping的value中设置一下即可

1
2
3
4
@RequestMapping(value = {"/say"}, method = RequestMethod.GET)
public String say(@RequestParam("id" , required=false , defaultValue= “2”) Integer id){
return "id="+id;
}

4.3.3 如何简化@RequestMapping(value = {“/say”}, method = RequestMethod.GET)呢

可以使用@GetMapping(value="/say")代替

五、jpa

5.1 什么是jpa

JPA(Java Persistcence API)定义了一系列对象持久化的标准。目前实现这一规范的产品有Hibernate、TopLink等。

使用mysql数据哭需要添加两个组件,在porm中添加

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

然后在application.yml中添加mysql驱动和jpa

1
2
3
4
5
6
7
8
9
10
11
12
spring:
profiles:
active: dev
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/dbgirl
username: root
password: 123456
jpa:
hibernate:
ddl-auto: create
show-sql: true

的ddl-auto 的值为create时,表示每次启动都创建一个新的Girl表(删除老表);当为update时,不会删除老表中的数据,会保留下来;当为create-drop时,当停下来时会删除表;当为validate时会验证表中的结构,如果不一致的化会报错

RESTful API的设计
| 请求类型 | 请求路径 | 功能 |
| — | — | — |
| GET | /girls | 获取女生列表 |
| POST | /girls| 创建一个女生 |
| GET | /girls/id | 通过id查询一个女生 |
| PUT | /girls/id | 通过id更新一个女生 |
| Delete | /girls/id | 通过id删除一个女生 |

5.2 创建数据库

比如要在数据库中创建一个dbgirl的表。不用使用sql语句的。可以直接在工程中创建一个Girl的类,并且加上@Entity;必须加一个无参的构造函数,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Girl{
@Id
@GeneratedValue
private Integer id;
private String cupSize;
private Integer age;

public Girl(){
}

^^get和set方法
}

这样运行一下,就直接在数据库中擦黄建了一个Girl的表,里面有相应的字段了。

5.3 数据库查询

如何通过spring boot查询数据库中的所有内容呢,首先我们需要建立一个接口继承JpaRepository

1
2
3
public interface GirlRepository extends JpaRepository<Girl,Integer> {

}

然后新建一个GirlController用@RequestController注解,然后在里面声明一个GirlRepository,最后给出一个girlList的函数即可,如下所示:

1
2
3
4
5
6
7
8
9
10
@RestController
public class GirlController {
@Autowired
private GirlRepository girlRepository;

@GetMapping(value = "/girls")
public List<Girl> girlList(){
return girlRepository.findAll();
}
}

这样就可以通过ip:port/path/girls来查询数据库中所有的女孩列表了

5.4 添加一个女生到数据库

在GirlController中声明一个函数即可,接受一系列的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 /**
* 添加一个女生
* @param cupSize
* @param age
* @return
*/
@PostMapping(value = "/girls")
public Girl girlAdd(@RequestParam("cupSize") String cupSize,
@RequestParam("age") Integer age){

Girl girl=new Girl();
girl.setCupSize(cupSize);
girl.setAge(age);

return girlRepository.save(girl);
}

这样就可以通过post请求传入相关参数来入库了,不需要一句sql语句。同理对女生的查询、更新、删除如下:

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
/**
* 根据id查询一个女生
* @param id
* @return
*/
@GetMapping(value = "/girl/{id}”)
public Girl girlFindOne(@RequestParam("id") Integer id){
return girlRepository.findOne(id);
}

/**
* 更新一个女生
* @param id
* @param cupSize
* @param age
* @return
*/
@GetMapping(value = "/girls/{id}”)
public Girl girlUpdate(@RequestParam("id") Integer id, @RequestParam("cupSize") String cupSize,
@RequestParam("age") Integer age){
Girl girl=new Girl();
girl.setId(id);
girl.setCupSize(cupSize);
girl.setAge(age);
return girlRepository.save(girl);
}

/**
* 删除一个女生
* @param id
*/
@GetMapping(value = "/girl/{id}”)
public void girlDelete(@RequestParam("id") Integer id){
girlRepository.delete(id);
}
5.5 自定义数据库查询接口

如果想要根据年龄查询数据库中的女生,怎么做呢。首先在GirlRepository中声明一个接口findByAge(注意这种格式):

1
2
3
4
5
6
7
8
9
public interface GirlRepository extends JpaRepository<Girl,Integer> {

/**
* 通过年龄查询
* @param age
* @return
*/
public List<Girl> findByAge(Integer age);
}

然后在GirlController中添加根据年龄查询的函数即可:

1
2
3
4
5
6
7
8
9
10
11
/**
* 通过年龄查询女生列表
* 需要在GirlRepositiry中声明接口
* @param age
* @return
*/
@GetMapping(value = "/girls/age/{age}")
public List<Girl> girlFindByAge(@RequestParam("age") Integer age){
return girlRepository.findByAge(age);

}

六、事务管理

作为单个逻辑单元执行的一系列的操作,要么完全执行,要么完全不执行
比如插入两个女生信息,要么同时成功,要么就同时都不成功;
创建一个Service,在里面声明GirlRepository,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class GirlService{
@Autiwired
private GirlRepository girlRepository;


@Transactional
public void insertTwo(){
Girl girlA=new Girl();
girlA.setCupSize("A");
girlA.setAge(18);
girlRepository.save(girlA);

Girl girlB=new Girl();
girlB.setcupSize("B");
girlB.setAge(19);
girlRepository.save(girlB);

}
}

加入的注解@Transactional 为事务注解,表示要么同时成功要么同时失败。不可能只成功一个
然后在GirlController中添加:

1
2
3
4
5
6
7
@Autowired 
private GirlService girlService;

@postMapping(value="/girls/two")
public void girlTwo(){
girlService.insertTwo();
}

此时在浏览器输入localhost:8080/girls/two即可插入insertTwo()中的两个数据。要么同时成功,要么同时失败。