JavaWeb学习记录——SpringBoot
(一)基本概念
(1)什么是MVC
M:模型层,指的是javaBean,作用是处理数据
- 一类称为实体类Bean:用于存储数据----Student、User
- 一类称为业务处理Bean: 指的Service或者Mapper对象,用于处理业务逻辑和数据访问
V:View,视图层,指的是html或jsp页面,作用是与用户进行交互、展示
C:controller,控制层,指的是servlet
(2)SpringMVC
其实就是Spring的一个子项目、后续产品
- 基于原生的Servlet,通过功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
比如实现转发、重定向、页面跳转之类,不用servlet语法
相当于就是把servlet封装了,我们自己就不用写了! - Spring家族原生产品,与IOC容器等基础设施无缝对接
(3) SpringBoot
1 配置文件
- resources下的配置文件
- 可以统一配置端口、数据库信息等
- 两种写法、两个文件可以同时存在且同时生效
(1)resources/application.properties
server.port=8080
(2)resources/application.yml
- 记得在 “ :”之后要写空格
- 缩进不允许tab,只允许空格
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mdc_db?characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
mvc:
static-path-pattern: /static/**
mybatis:
configuration:
map-underscore-to-camel-case: true
- 使用小驼峰命名:数据库可以找到数据 map-underscore-to-camel-case: true
2 把项目打成jar包,直接在目标服务器执行即可
- 要关闭cmd的快速编辑模式
3 依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
父项目声明了版本号,子项目依赖父项目的版本号,不用写版本号了:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
如果要更改子项目的版本号:
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
- mysql
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
- 文件上传工具类–>
<!-- 文件上传工具类-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
4 starter场景启动器
1.见到很多spring-boot-starter-*: *就某种场景
2.只要引入starter,这个场景所有常规需要的依赖就会被引用
5 请求处理 常用参数的处理
(1)@PathVariable 路径变量
请求: <a href="car/3/owner/zhangsan"> 获取信息 </a>
处理:
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("pv",pv);
return map;
}
结果:
{"pv":{"id":"3","username":"zhangsan"},"name":"zhangsan","id":3}
(2)@RequestParam 请求参数
请求: <a href="car/3/owner/zhangsan?age=18&inters=basketball&inters=game"> 获取信息 </a>
处理:
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String>){
Map<String,Object> map = new HashMap<>();
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
return map;
}
结果:
{"inters":["basketball","game"],"param":{"age":"18","inters":"baskterball","age":18}}
(3)@ResponseBody
ResponseBody详解https://blog.csdn.net/originations/article/details/89492884
@ResponseBody并不是以json返回。不加@ResponseBody,是将方法返回的值作为视图名称,并自动匹配视图去显示,而加上@ResponseBody就仅仅是将方法返回值当作内容直接返回到客户端,并且会自适应响应头的content-type,返回的字符串符合json,那么content-type就是application/json,如果是普通字符串,就是text/plain,但是加上注解属性produces=application/json,那么不管内容是什么格式,响应头的content-type就一直是application/json,不再去做自适应,至于内容是不是json都不重要了
(4)@RequestAttribute
获取attribute中的数据
request.setAttribute(“msg”,“111”);
@RequestAttribute(“msg”) String msg
(5)@PostMapping @GetMapping
127.0.0.1/login
@getmapping("/","/login") url映射
@postmapping
<lable th:text="${msg}"></label>
<form th:aciton="@{/login}"></form>
@postmapping("/login") 表单
public String main(User user,HttpSession session){
if(StringUtils.hasLength(user.getUserName()&& StringUtils.hasLength(user.getPassword()))){
session.setAttribute("loginUser",user);
//如果写成"main" 会导致表单重复提交
return "redirect:/main.html"; //重定向到main,防止表单重复提交
}
else{
model.addAttribute("msg","账号密码错误");
return "login"
}
}
去main页面 避免了重复刷新,反复登录
@GetMapping("/main.html")
public String mainPage(HttpSession session){
//判断是否登录,拦截器,过滤器
Objecet loginUser = session.getAttribute("loginUser");
if(loginUser!=null){
return "main";
}
else{
model.addAttribute("msg","请重新登录");
return "login"
}
}
6 配置文件
(1)@Configuration //告诉SpringBoot这是一个配置类
configuration.java
@Configuration //告诉SpringBoot这是一个配置类
public class MyConfig{
@Bean
//给容器中添加组件,以方法名作为组件的id,返回类型就是组件类型;返回的值,就是组件在容器中的实例
public User user01(){
return new User("zhangsan",18);
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
StartUpApplication.java
....
public class StartUpApplication{
public static void main(String[] args){
//1、返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(StartUpApplicatio.class,args);
//2、查看容器里的组件
Sring[] names = run.getBeanDefinitionNames();
for(String name : names){
System.out.println(name);
}
//3、从容器中获取组件
Pet tom =run.getBean("tom",Pet.class);
}
}
7 复杂参数
(1)map、model
map、model里面的数据会被放在request请求域中 即自动调用request.setAttribute
@GetMapping("/params")
public String testParam(Map<String,Object> map, Model model,HttpServletRequest request,HttpServletResponse response){
map.put("hello","world555");
model.addAttribute("world","hello");
request.setAttribute("msg","hello1");
Cookie cookie = new Cookie("c1","v1");
cookie.setDomain("localhost");
response.addCookie();
return "foward:/sucess";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg,)
(2)redirectAttribute 重定向携带数据
8 拦截器
- 目标方法执行之前 prehandle
- 目标方法执行完成 posthandle
- 页面渲染之后 afterhandle
// 登陆检查
// 1、配置好拦截器要拦截哪些请求
// 2、把拦截器放在容器中
@Slf4j
public class LoginInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handle){
String requestURI = request.getRequestURI();
log.info("拦截的请求路径是{}",requestURI);
//登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser!=null){
//放行
return true;
}
//拦截 未登录,跳转到登录页
session.setAttribute("msg","请先登录");
response.sendRedirect("/");
return false;
}
}
Adminconfig.java
@configuration
public class Adminconfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //拦截 /**表示拦截所有,静态资源也会被拦截
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**") //放行的
}
}
(二)基本逻辑
(1)由controller层的PathController决定第一个页面:return: login页面
//pathController.java
@Controller
public class PathController {
@GetMapping("/")
public String welcome(){
return "login";
}
}
(2)login.html中的表单<form>
收集用户的数据,并交给LoginController.java里的login函数处理
这里是通过@PostMapping(“/login”)检测
<!--login.html-->
<form id="form1" method="post" action="login" target="_blank">
<p><input type="submit" value="登 录" /></p></form>
表单将以post(method=“post”)方法发送到login(aciton=“login”)由此定位:@PostMapping(“/login”)
//注入登录及注销服务接口对象
@Resource //Autowired或Resource 都可以 Resource可选的方式更多
private ILoginService iloginService;
//LoginController.java
@PostMapping("/login") //@PostMapping,处理post请求
public String login(Model model, String userName,String password){
try{
//访问登录服务接口
String nextPath=iloginService.login(model,userName,password);
//返回下一个视图的路径
return nextPath;
}
catch(Exception e){
e.printStackTrace();
}
return "error";
}//这里的userName要和login.html中的对应
method=“post” action=“login”------> @PostMapping(“/login”)
(3)login函数调用iLoginService接口中的login函数:String nextPath = iLoginService.login(model, userName, password);
返回下一个视图的路径 return nextPath
@Service
public class LoginServiceImpl implements ILoginService{
//注入数据访问映射接口对象
@Resource
private ITMavenDemoUserMapper itMavenDemoUserMapper;
@Override
public String login(Model model,
String userName,String password) throws Exception {
//访问数据映射接口,条件查询数据
//数据类型:TMavenDemoUser 调用接口:itMavenDemoUserMapper
TMavenDemoUser user = itMavenDemoUserMapper.queryByLogin(userName, password);
//用户登陆验证
if(user!=null) { //登陆成功
//将用户信息 封装到 会话缓存中
model.addAttribute("user",user);
return "index"; //返回首页
}else{ //登陆失败
model.addAttribute("error","用户名或密码错误");
}
return "login";//或者return "/" 其实就是根路径
// “/”不行 http://127.0.0.1:80/ 会自动加后缀html 所以/不行
}
(4)上述login函数调用该java文件中的对象itMavenDemoUserMapper的queryByLogin函数TMavenDemoUser user = itMavenDemoUserMapper.queryByLogin(userName, password);
//数据类型:TMavenDemoUser 调用接口:itMavenMavenDemoUserMapper
(5)ITMavenDemoUserMapper.java定义了ITMavenDemoUserMapper接口(该接口自己实现了查询功能)
ITMavenDemoUserMapper接口:
public interface ITMavenDemoUserMapper {
/**条件查询:登录
* @param userName
* @param password
* @return
* @throws Exception
**/
@Select("select * from t_mavendemo_user where user_name=#{userName} and user_password=#{password}")
public TMavenDemoUser queryByLogin(@Param("userName")String userName,@Param("password") String password) throws Exception;
(6)查询数据库,将查询结果返回(4)中的user对象
TMavenDemoUser user = itMavenDemoUserMapper.queryByLogin(userName, password);
(7)if…else判断是否得到user,进行对应操作;
@Override
public String login(Model model,
String userName,String password) throws Exception {
//访问数据映射接口,条件查询数据
TMavenDemoUser user = itMavenDemoUserMapper.queryByLogin(userName, password);
//用户登陆验证
if(user!=null) { //登陆成功
//将用户信息 封装到 会话缓存中
model.addAttribute("user",user);
return "index"; //返回首页
}else{ //登陆失败
model.addAttribute("error","用户名或密码错误");
}
return "login";//或者return "/" 其实就是根路径
// “/”不行 http://127.0.0.1:80/ 会自动加后缀html 所以/不行
}
(8)得到user,封装数据到会话缓存,返回index.html页面
model.addAttribute("user", user);return "index";
(三)项目搭建
(1)创建maven项目
在IDEA中创建maven项目,选中Create from archetype中的org.apache.cocoon:cocoon-22-archetype-webapp
(2) 声明包类型: java、resource
声明包类型: java、resource
webapp->WEB-INF web.xml(确认已创建v)
创建data包/model包:是数据模型存放的地方
创建mapper包,创建mapper接口
创建service包,创建service实现类和接口
创建controller包:创建Controller类
安装MYSQL、Navicat等等…
(3) 在pom.xml文件中添加依赖
我自己常用的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
<version>0.0.20131108.vaadin1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
<version>0.0.20131108.vaadin1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
日志的jar包:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
- 在template中html添加模板引擎;
<html xmlns:th="http://www.thymeleaf.org">
(5)在application中配置数据库、服务器信息
#此配置文件一定要放在resources下
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver # 8版本的数据库选择cj版本 5版本的选择没有cj的
url: jdbc:mysql://127.0.0.1:3306/maven_demo?characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
mybatis:
configuration:
map-underscore-to-camel-case: true #开启驼峰命名 记得开记得开!!!不然数据库找不到值!!!
(6) 启动服务器:StratApplication
- 注意StartUpApplication.java启动文件不能直接放在java文件下 需要java.demo一个文件
(报错信息 StartUpApplication in default package)
package com;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**启动服务器**/
@SpringBootApplication //加入注解
@MapperScan("com/database/mapper") //从mapper中复制路径 会通过此注解扫描Mapper下所有接口
public class StartUpApplicaiton {
public static void main(String[] args) {
SpringApplication.run(StartUpApplicaiton.class,args);
}
}
(7)创建请求控制器–controller
就是一个POJO(普通java类,不需要继承,也不需要实现)
@controller (标识为控制层组件)
(8)
(四)项目流程 速写
一般写项目是从后往前写比较快 pwp
(1)Mapper——在Mapper层创建数据模型TMavenDemoUser(数据库名类)
自动补全getter和setter —>alt+insert 全选 然后OK
public class TMavenDemoUser {
private Integer userId;
private String userName;
private String userGender;
private String userPassword;
private Date userBirthday; //此date一定要写成sql.Date import java.sql.Date;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}
(2)在mapper接口中进行mybatis查询书写
命名方式:ITMavenDemoUser(Interface+数据库名)= 接口名
public interface ITMavenDemoUserMapper {
/**条件查询:登录
* @param userName
* @param password
* @return
* @throws Exception
**/
@Select("select * from t_mavendemo_user where user_name=#{userName} and user_password=#{password}")
public TMavenDemoUser queryByLogin(@Param("userName")String userName,@Param("password") String password) throws Exception;
(3)在service层中创建接口 IUserService
(interface+接口名)
- 书写抽象方法
- 注意:返回String类型
public interface IUserService {
public String query(Model model,String userName)throws Exception;
public String addView(Model model,Integer id)throws Exception;
public String updateView(Model model,Integer id)throws Exception;
(4)在service层中书写具体实现类——UserServiceImpl
用alt+insert补全Override方法
@Service
public class UserServiceImpl implements IUserService {
//注入数据访问映射接口
@Resource
private ITMavenDemoUserMapper itMavenDemoUserMapper;
@Override
public String query(Model model, String userName) throws Exception {
//调用 数据访问接口的查询方法
List<TMavenDemoUser> list=itMavenDemoUserMapper.queryAll(userName);
//封装数据到模型中
model.addAttribute("list","list");
return "admin/user/userlist";
}
@Override
public String addView(Model model, Integer id) throws Exception {
return "admin/user/useraddview";
}
@Override
public String updateView(Model model, Integer id) throws Exception {
//条件查询用户数据
TMavenDemoUser user=itMavenDemoUserMapper.queryById(id);
model.addAttribute("user",user);
return "admin/user/userupdateview";
}
(5)在controller创建类 UserController
-
一些注解:
@RestController 导入的是json对象
@Controller+@responsbody=@RestController -
写出框架:try catch
public String query(Model model, String suserName){ //为了区分,避免与添加修改起冲突
try{
} catch(Exception e){
e.printStackTrace();
}
return "error";
}
public String addView(Model model){
try{
} catch(Exception e){
e.printStackTrace();
}
return "error";
}
public String updateView(Model model,Integer id){
try{
} catch(Exception e){
e.printStackTrace();
}
return "error";
}
- 添加注释和注解
@RequestMapping("/user/query")
@RequestMapping("/user/addview")
@RequestMapping("/user/updateview")
- 注入服务接口对象
@Resource //通过resource给接口注入一个实现类对象
private IUserService iUserService; //IOC容器帮助创建
- 添加try
try{
String nextPath=iUserService.query(model, suserName);
return nextPath;
}
try{
String nextPath = iUserService.addView(model,id);
return nextPath;
}
(6)添加前端页面
- 添加
<base>
基本路径:<base href="/" />
- 添加html thymeleaf标签(模板引擎)
<html xmlns:th="http://www.thymeleaf.org">
– 运行后 可以打开 target class 看有无编译成功
(五)一些小知识点
将模板引擎和js结合:
<script th:inline="javascript"> <!--定义一个js变量a var a -->
var error = [[${error}]];
</script> <!--inline内嵌类型-->
<script> <!--定义一个js变量a var a -->
var error = null;
</script> <!--inline内嵌类型-->
- 可以利用检查来查看是否有错
(2)@PathVariable
@RequestMapping("/user/updateview/{id}")
public String updateView(Model model,@PathVariable("id") Integer id){
这里的路径变量@PathVariable (“id”)和上面的"/user/updateview/{id}"对应,会将{id}拿出来给Integer id
(3) @RequestParam
public String updateView(Model model,@RequestParam("id") Integer ids){
//如果前端和控制器请求参数的名称不一样 需要使用@RequestParam(“id”)String ids这样来注解
//会将id的变量值赋给ids,如果相同,则不用书写
(三)注解说明
–>java bean 是一个可复用的类,javaBean在MVC设计模型中是model,又称模型层,在一般的程序中,我们称它为数据层,就是用来设置数据的属性和一些行为。
@controller 注解:
用来响应页面,表示当前的类为控制器,它还允许自动检测定义在类路径下的组件并自动注册
@RequestMapping:
当前台界面调用Controller处理数据时候告诉控制器怎么操作
作用:URL映射。
@GetMapping:
@RequestMapping(method = RequestMethod.GET)的简写
作用:对应查询,表明是一个查询URL映射
GetMapping里边的内容是路径名字,前端控制器通过解析浏览器传来的URL地址,对应到响应的Mapping下的函数进行执行
@GetMapping("/")
public String welcome(){
return "login";
}
即前端浏览器页面是127.0.0.1/ 会被对应到public String welcome(){}函数下执行
@PostMapping:
@RequestMapping(method = RequestMethod.POST)的简写
作用:对应增加,表明是一个增加URL映射
@SessionAttributes注解
用于在请求之间的HTTP Servlet会话中存储model属性
(四)java复习
(1)接口
interface 接口名{
常量或方法
}
特点:
- 所有的属性都是public static
- 所有的方法都是public abstract 方法,没有构造方法
- 接口不能创建对象
- 接口只能声明引用
- 接口名必须与文件名完全相同
与类的关系:
- 实现类实现接口
class Impl implements IA {
@Override
public void test(){
}
}
(2)List<E>
继承colloction接口
E:数据类型
(一)<form></form>
标签
<input>
用于搜索用户信息:输入字段可以是文本、复选框、按钮"
<input type="text" name="firstname" value="Sunny">
(1)type:是input的类型,
text是普通文本,
password会将输入的自动变为密码形式,submit是提交
button…
<input type="password" name="password" autofocus placeholder="密码">
(2)value:是显示出来的值
(3)name:定义input元素的名称
(4)placeholder:占位符,会在空的位置占据
required placeholder:此字段必须输入
autofocus placeholder:定义输入字段在页面加载时是否获得焦点
<form id="form1" method="post" action="login">
<p><input type="text" name="userName" autofocus required placeholder="用户名" class="indent"/></p>
<p><input type="password" name="password" autofocus placeholder="密码" class="indent" /></p>
<p class="error">[[${error}]]</p>
<p><input type="submit" value="登 录" /></p>
</form>
- submit:如果点击登录(value),表单将被发送到login界面(action=“login”)
- method:method定义如何发送表单数据(表单数据发送到action属性所规定的页面)
**(1)作为http post(method=“post”)的方式来发送。**分两步:首先浏览器将与action属性中指定的表单处理服务器建立联系,联系建立后,浏览器会按照分段传输的方法将数据发送给服务器。
在服务器端,一旦 POST 样式的应用程序开始执行时,就应该从一个标志位置读取参数,而一旦读到参数,在应用程序能够使用这些表单值以前,必须对这些参数进行解码。用户特定的服务器会明确指定应用程序应该如何接受这些参数。
**(2)作为URL变量(method=“get”)**这时浏览器会与表单处理服务器建立连接,然后直接在一个传输步骤中发送所有的表单数据:浏览器会将数据直接附在表单的 action URL 之后。这两者之间用问号进行分隔。
一般浏览器通过上述任何一种方法都可以传输表单信息,而有些服务器只接受其中一种方法提供的数据。可以在 标签的 method (方法)属性中指明表单处理服务器要用方法来处理数据,使 POST 还是 GET。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
宽度高度 包含边框:box-sizing: border-box
解决尺寸不同的问题
hover: 鼠标移动前后样式不同
outline:none 去除外部边框
js中的变量尽量用let声明