工具准备
- 安装java,并配置环境变量(JAVA_HOME:配置JDK安装路径,PATH:配置JDK命令文件的位置,CLASSPATH:配置类库文件的位置)
- 安装maven,添加环境变量M2_HOME,这是一个构建工具,用于项目的构建
- 下载
IntelliJ IDEA
编辑器,并安装,这是一个ide
项目构建
- 新建项目:打开idea新建Spring Initializr的项目,Project SDK就选上一步配置的那个java,我选的java 1.8,Name写上自己的项目名称(英文),Dependencies(依赖)选择 Web/Web,然后选择Project location(项目目录),Finish就构建好了
热部署
- stop1、POM文件添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
- stop2、POM文件添加插件
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <!--fork : 如果没有该项配置devtools不会起作用,即应用不会restart --> <fork>true</fork> <addResources>true</addResources><!--支持静态文件热部署--> </configuration> </plugin>
- stop3、
mvn spring-boot:run
启动项目,之后项目中源码的修改就会动态生效了,不需要手动重启server - 注意 idea一定要把下面的配置勾选上
Preferences
>Build,Execution,Deployment
>Compiler
>Build project automatically
-
目录结构
- pom.xml (Maven 是一个跟npm一样的工具,只有项目下有一个pom.xml (相当于npm里的pagacke.json),在项目下使用
mvn help:system
命令就可以下载相关的包了,使用mvn spring-boot:run
启动项目) - target/ 编译后的文件
- src/ 源代码目录
- src/test/ 测试目录
- src/main/ 主目录
- src/main/java/ java源代码文件目录
- src/main/java/com.example.demo/ 项目包目录,在这个里面写代码
- src/resources/ 项目配置文件目录
- src/resources/application.properties 配置文件
-
项目目录组织
把全部源代码文件都放到项目包目录下
src/main/java/com.example.demo/
是可以的,但不方便我们编码,我们需要分割一下,这样方便查找编码,下面是我的分割方式 - controller - Controller 层
- service - 业务逻辑层
- dao - 数据操作层 DAO
- domain - 实体类
-
配置
配置写在
src/resources/application.properties
文件中,为了方便我们在不同场景下使用不同配置,我们还需要建立几个文件,比如我们有以下的几种场景 - 公司 application-company.properties
- 家 application-home.properties
- 生产模式 application-prod.properties
-
在主配置文件
application.properties
中,配置一个配置选项,比如我们的场景是在公司spring.profiles.active=company
常用配置
-
server.port=9090 # 服务端口号 server.tomcat.uri-encoding=UTF-8 #以Tomcat为web容器时的字符编码 spring.application.name=customer # 应用名称,一般就是项目名称,这个名称在SpringCloud中比较关键 spring.profiles.active=dev #指定当前的活动配置文件,主要用于多环境多配置文件的应用中 spring.http.encoding.charset=UTF-8 #http请求的字符编码 spring.http.multipart.max-file-size=10MB #设置文件上传时单个文件的大小限制 spring.http.multipart.max-request-size=100MB #设置文件上传时总文件大小限制 spring.thymeleaf.prefix=classpath:/templates/ #配置在使用Thymeleaf做页面模板时的前缀,即页面所在路径 spring.thymeleaf.suffix=.html #设置在使用Thymeleaf做页面模板时的后缀 spring.thymeleaf.cache=false #设置在使用Thymeleaf做页面模板时是否启用缓存 spring.mvc.static-path-pattern=/** #设置静态资源的请求路径 spring.resources.static-locations=classpath:/static/,classpath:/public/ #指定静态资源的路径 ##以下是使用MySQL数据库的配置 hibernate.dialect=org.hibernate.dialect.MySQL5Dialect #指定数据库方言 hibernate.show_sql=true #是否显示sql语句 hibernate.hbm2dll.auto=update #设置使用Hibernate的自动建表方式 entitymanager.packagesToScan=com.zslin #设置自动扫描的包前缀 spring.datasource.url=jdbc:mysql://localhost:3306/customer?\ useUnicode=true&characterEncoding=utf-8&useSSL=true&autoReconnect=true #数据库链接 spring.datasource.username=root #数据库用户名 spring.datasource.password=123 #数据库用户对应的密码 spring.datasource.driver-class-name=com.mysql.jdbc.Driver #数据库驱动名称
@RestController //这个注解表明这个类是个controller类 @RequestMapping(value="/api/user") //这个注解起到路由的作用 public class UserController { @RequestMapping(value = "/{id}",method = RequestMethod.GET) public String findUserById(@PathVariable Long id){ return "hello world"+id; } }
mybatis
配置依赖
pom.xml中配置mybatis依赖
<!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <!-- MySQL 连接驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency>
需要了解的几个注解
- @Select 是查询类的注解,所有的查询均使用这个
- @Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属 性和数据库属性名保持一致,就不需要这个属性来修饰。
- @Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值
-
domain
在domain新建我们的数据实体类,domain下面的每个类对应数据表的一张表,每个属性也对应表中的一个字段
- @Update 负责修改,也可以直接传入对象
- @delete 负责删除
-
public class User { private Long id; private String username; private String password; private String phone; private Integer sex; .......
dao
MyBatis的Dao与其它的ORM框架不一样的是,MyBatis的Dao其实就是Mapper,是一个接口,是通过MapperScannerConfigurer扫描后生成实现的,我们不需要再写Dao接口的实现。
-
@Mapper public interface UserDao { @Select("SELECT * FROM user WHERE id = #{id}") @Results({ @Result(property = "id", column = "id"), @Result(property = "username", column = "username"), @Result(property = "password", column = "password"), @Result(property = "phone", column = "phone"), @Result(property = "sex", column = "sex") }) User findUserById(@Param("id") Long id); @Select("SELECT * FROM user") List<User> getAll(); }
service
service为业务处理及事务层,建立一个接口 service/UserService.java
-
public interface UserService { User findUserById(Long id); }
然后建立一个实体类来实现这个接口,service/impl/UserServiceImpl.java
-
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; public User findUserById(Long id){ return userDao.findUserById(id); } }
controller
controller层,负责路由
-
@RestController @RequestMapping(value = "/api/user") public class UserController { @Autowired private UserService userService; @RequestMapping(value = "/{id}",method = RequestMethod.GET) public User findUserById(@PathVariable Long id){ return userService.findUserById(id); } } ## 使用session ##
@GetMapping("/getSession") public @ResponseBody JData getSession(HttpServletRequest request){ HttpSession session = request.getSession(); session.setAttribute("account","hahaha"); Object account = session.getAttribute("account"); return new JData(1,"ok",account); }
拦截器HandlerInterceptor
自定义拦截器 MyInterceptor
-
/自定义拦截器 public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception{ System.out.println(">>>MyInterceptor1>>>>>>>在请求处理之前进行调用(Controller方法调用之前)"); return false;//只有返回true才会继续向下执行,返回false取消当前请求 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{ System.out.println(">>>MyInterceptor1>>>>>>>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)"); } @Override public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) throws Exception{ System.out.println(">>>MyInterceptor1>>>>>>>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)"); } }
将自定义拦截器手动添加到拦截器链中
-
@Configuration public class MyWebAppConfigurer extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry){ // 多个拦截器组成一个拦截器链 // addPathPatterns 用于添加拦截规则 // excludePathPatterns 用户排除拦截 registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); super.addInterceptors(registry); } }
log4j
引入log4j依赖
在创建Spring Boot工程时,我们引入了 spring-boot-starter ,其中包含了 spring-boot-starter-logging ,该依赖内容就是Spring Boot默认的日志框架Logback,所以我们在引入log4j之前,需要先排除该包的依赖,再引入log4j的依赖,就像下面这样:
<!--添加log4j 排除spring-boot-starter-logging依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
配置log4j.properties
在引入了log4j依赖之后,只需要在 src/main/resources 目录下加入 log4j.properties 配置文件,就可以开始对应用的日志进行配置使用。
# LOG4J配置 log4j.rootCategory=Info, stdout, file, errorfile log4j.category.com.didispace=DEBUG, didifile log4j.logger.error=errorfile # 控制台输出 log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n # root日志输出 log4j.appender.file=org.apache.log4j.DailyRollingFileAppender log4j.appender.file.file=logs/all.log log4j.appender.file.DatePattern='.'yyyy-MM-dd log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n # error日志输出 log4j.appender.errorfile=org.apache.log4j.DailyRollingFileAppender log4j.appender.errorfile.file=logs/error.log log4j.appender.errorfile.DatePattern='.'yyyy-MM-dd log4j.appender.errorfile.Threshold = ERROR log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout log4j.appender.errorfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n # com.didispace下的日志输出 log4j.appender.didifile=org.apache.log4j.DailyRollingFileAppender log4j.appender.didifile.file=logs/my.log log4j.appender.didifile.DatePattern='.'yyyy-MM-dd log4j.appender.didifile.layout=org.apache.log4j.PatternLayout log4j.appender.didifile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L ---- %m%n
使用log4j
package com.example.demo.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @RestController public class TestController { private static final Logger logger = LogManager.getLogger(TestController.class); @RequestMapping("/hello") public String hello(){ logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); return "hello world"; } }
AOP
spring-boot中使用AOP作为拦截器,拦截通知类型有:前置通知、后置最终通知、后置返回通知、后置异常通知、环绕通知
@Before 前置通知,方法调用前被调用 @AfterReturning 后置返回通知 @AfterThrowing 后置异常通知 @After 后置最终通知 @Around 环绕通知
AOP依赖配置
使用AOP必须在
pom.xml
中添加引用<!--添加spring-boot-starter-aop依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
AOP使用
新建
aspect/HttpAspect.java
package com.example.demo.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect @Component public class HttpAspect { private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class); /** * 前置通知,方法调用前被调用 * @param joinPoint */ @Before("execution(public * com.example.demo.controller.TestController.hello(..))") public void before(){ logger.info("我是前置通知!!!"); } /** * 后置返回通知 * 这里需要注意的是: * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息 * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数 * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值 * @param joinPoint * @param keys */ @AfterReturning(value = "execution(public * com.example.demo.controller.TestController.hello(..))",returning = "keys") public void afterReturning(JoinPoint joinPoint,Object keys){ logger.info("后置返回通知的返回值:"+keys); } /** * 后置异常通知 * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法; * throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行, * 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。 * @param joinPoint * @param exception */ @AfterThrowing(value = "execution(public * com.example.demo.controller.TestController.hello(..))",throwing = "exception") public void afterThrowing(JoinPoint joinPoint,Throwable exception){ logger.info("目标方法名:"+joinPoint.getSignature().getName()); if(exception instanceof NullPointerException){ logger.info("发生了空指针异常!!!!!"); } } /** * 后置最终通知(目标方法只要执行完了就会执行后置通知方法) * @param joinPoint */ @After("execution(public * com.example.demo.controller.TestController.hello(..))") public void after(){ logger.info("后置通知执行了!!!!"); } /** * 环绕通知: * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。 * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 */ @Around("execution(public * com.example.demo.controller.TestController.hello(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint){ logger.info("环绕通知的目标方法名:"+proceedingJoinPoint.getSignature().getName()); try {//obj之前可以写目标方法执行前的逻辑 Object obj = proceedingJoinPoint.proceed();//调用执行目标方法 return obj; } catch (Throwable throwable) { throwable.printStackTrace(); } return null; } }
统一异常处理
在程序需要抛出异常时,我们可以throw new Exception("发生错误")
- 创建一个自定义异常,用来实验捕获该异常,并返回json
public class MyException extends Exception { public MyException(String message) { super(message); } }
- 创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据
-
public class ErrorInfo<T> { private Integer code; private String message; private T data; // 省略getter和setter }
- 为MyException异常创建对应的处理
-
@RestController //注解这个类为controller类 @RequestMapping(value = "/api/user") //这里设置api请求前缀 public class UserController { // 获取一条数据 @RequestMapping(value = "/{id}",method = RequestMethod.GET) public JData findUserById(@PathVariable Long id) throws MyException { if (id<=0){ throw new MyException(1,"发生错误","id不能为空"); } User u = userService.findUserById(id); return new JData(1,"ok",u); } .....