广告推广的好处/惠州百度关键词优化
以下转载和参考自:Spring Boot开发 - 廖雪峰的官方网站
1、配置Spring Boot
SpringBoot包含了Spring MVC和Tomca、日志等,启动SpringBoot就开启了Spring MVC和Tomcat。一个Spring Boot项目的组织结构如下所示,可以看到,Spring Boot中已经不需要专门的webapp目录了:
springboot
├── pom.xml
├── src
│ └── main
│ ├── java
│ ├── xsl
│ ├── Application.java
│ ├── entity
│ │ └── User.java
│ ├── service
│ │ ├── UserMapper.java
│ │ └── UserService.java
│ └── web
│ └── UserController.java
│ └── resources
│ ├── application.yml
│ ├── logback-spring.xml
│ ├── static
│ ├── css
│ └── bootstrap.css
│ └── js
│ ├── bootstrap.js
│ └── jquery.js
│ └── templates
│ ├── hello.html
│ └── signin.html
└── target
其中resources目录下的application.yml为Spring Boot默认配置文件,也可以使用.properties文件,如下所示为其和.properties的对比:
# application.ymlserver:#先从环境变量中查找APP_PORT,查找不到的话再使用8080对port赋值,可以在测试环境中使用默认值,正式环境使用环境变量中设置的值port: ${APP_PORT:8080}spring:application:#先从环境变量中查找APP_NAME,查找不到的话再使用app_name对port赋值,可以在测试环境中使用默认值,正式环境使用环境变量中设置的值name: ${APP_NAME:app_name}datasource:url: jdbc:mysql://127.0.0.1:3306/my_database?serverTimezone=GMT&useUnicode=true&characterEncoding=UTF-8&useSSL=falseusername: sapassword: 123456#使用mysql数据库驱动driver-class-name: com.mysql.cj.jdbc.Driver#配置JDBC连接池hikari:auto-commit: falseconnection-timeout: 3000validation-timeout: 3000max-lifetime: 60000maximum-pool-size: 20minimum-idle: 1pebble:# 默认为".pebble",改为"":suffix:# 开发阶段禁用模板缓存:cache: false
# application.propertiesspring.application.name = ${ APP_NAME:unnamed }spring.datasource.url = jdbc:hsqldb:file:testdbspring.datasource.username = saspring.datasource.password =spring.datasource.dirver - class - name = org.hsqldb.jdbc.JDBCDriverspring.datasource.hikari.auto - commit = falsespring.datasource.hikari.connection - timeout = 3000spring.datasource.hikari.validation - timeout = 3000spring.datasource.hikari.max - lifetime = 60000spring.datasource.hikari.maximum - pool - size = 20spring.datasource.hikari.minimum - idle = 1io.pebbletemplates.pebble.suffix = io.pebbletemplates.pebble.cache = false
如上所示,对于地址、用户名、密码这种类型的配置信息,可以使用环境变量,我们可以在启动程序时传入环境变量,如java -D PASSWORD=123456 -jar app.jar。
模板引擎中一些常用的默认属性值:
pebble.prefix=/templates/ 模板前缀,这里模板必须放在`/templates`目录
pebble.suffix=.pebble 模板后缀
pebble.encoding=UTF-8 模板编码
pebble.cache=true 使用模板缓存
pebble.content-type=text/html
pebble.defaultLocale=null
pebble.strictVariables=false
resources下的logback-spring.xml(也可以使用logback.xml)是日志logback配置文件,如下为一个标准的写法,它定义了一个控制台输出和文件输出,其中第三行<include resource=.../>引入了Spring Boot的一个缺省配置,这样我们就可以引用CONSOLE_LOG_PATTERN、FILE_LOG_PATTERN等变量:
<?xml version="1.0" encoding="UTF-8"?>
<configuration><include resource="org/springframework/boot/logging/logback/defaults.xml" /><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${CONSOLE_LOG_PATTERN}</pattern><charset>utf8</charset></encoder></appender><appender name="APP_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender"><encoder><pattern>${FILE_LOG_PATTERN}</pattern><charset>utf8</charset></encoder><file>app.log</file><rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"><maxIndex>1</maxIndex><fileNamePattern>app.log.%i</fileNamePattern></rollingPolicy><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><MaxFileSize>1MB</MaxFileSize></triggeringPolicy></appender><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="APP_LOG" /></root>
</configuration>
如下为pom.xml中引入的相关依赖,其中的<parent>...</parent>表示从spring-boot-starter-parent继承,这样就可以引入Spring Boot的预置配置。然后我们引入了Spring Boot、模板引擎(视图解析器)pebble、JDBC驱动的依赖。可以看到引入的Spring Boot没有指定版本号,因为引入的<parent>内已经指定了,引入的JDBC驱动也没有指定版本号,因为hsqldb已在spring-boot-starter-jdbc中预置了版本号:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><!-- 从spring-boot-starter-parent继承 --><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.0.RELEASE</version></parent><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>Spring-Boot</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><!-- 设置使用的版本 --><java.version>11</java.version><pebble.version>3.1.2</pebble.version><mybatis.version>3.5.4</mybatis.version><mybatis-spring.version>2.0.4</mybatis-spring.version></properties><dependencies><!-- 集成Spring Boot --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 集成Spring Boot JDBC--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- 集成Pebble View --><dependency><groupId>io.pebbletemplates</groupId><artifactId>pebble-spring-boot-starter</artifactId><version>${pebble.version}</version></dependency><!--MyBatis--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>${mybatis.version}</version></dependency><!--集成MyBatis与Spring的库--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>${mybatis-spring.version}</version></dependency><!-- JDBC驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies></project>
Application 为Spring Boot启动类。Spring Boot要求main()方法所在的启动类必须放到根package下,我们项目中的根package为xsl,xsl下还有xsl.entity、xsl.service、xsl.web等子package。Spring Boot启动类使用注解@SpringBootApplication,该注解实际上又包含了:
- @SpringBootConfiguration
- @Configuration
- @EnableAutoConfiguration
- @AutoConfigurationPackage
- @ComponentScan
/*Application.java*/package xsl;@SpringBootApplication
@MapperScan("xsl.service")
public class Application { //Spring Boot启动类public static void main(String[] args) throws Exception {SpringApplication.run(Application.class, args); //启动Spring Boot}@Bean//提供创建WebMvcConfigurer方法,使Spring MVC自动处理静态文件,并且映射路径为/static/**WebMvcConfigurer createWebMvcConfigurer(@Autowired HandlerInterceptor[] interceptors) {return new WebMvcConfigurer() {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// 映射路径`/static/`到classpath路径:registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");}};}@Bean //提供给MyBatis创建SqlSessionFactoryBean对象的方法SqlSessionFactoryBean createSqlSessionFactoryBean(@Autowired DataSource dataSource) {var sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;}
}
下面是JavaBean、Service、Controller的代码:
/*User.java*/package xsl.entity;public class User {private Long id;private String email;private String password;private String name;private long createdAt;public Long getId() {return id;}public void setId(Long id) {this.id = id;}......
}
/*UserMapper.java*/package xsl.service;//User类和数据库users表之间映射的Mapper
public interface UserMapper {//SELECT查询方法@Select("SELECT * FROM users WHERE id = #{id}") //getById()方法里对应执行的SQL语句User getById(@Param("id") long id); //通过主键获得一行记录的方法,@Param指示该参数与SQL语句中"id"参数对应@Select("SELECT id, name, created_time AS createdAt FROM users LIMIT #{offset}, #{maxResults}") //User类成员名要与查询记录的列名相同,列名和类属性名不同的话使用ASList<User> getAllLimit(@Param("offset") int offset, @Param("maxResults") int maxResults);//INSERT插入方法//如果表的id是自增主键,那么,我们在SQL中不传入id,但希望获取插入后的主键,需要再加一个@Options注解@Options(useGeneratedKeys = true/*设置自增主键*/, keyProperty = "id"/*主键值使用JavaBean的id属性值*/, keyColumn = "id"/*设置主键列名*/)@Insert("INSERT INTO users (email, password, name, createdAt) VALUES (#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})")void insert(@Param("user") User user);//UPDATE更新方法@Update("UPDATE users SET name = #{user.name}, createdAt = #{user.createdAt} WHERE id = #{user.id}")void update(@Param("user") User user);//DELETE删除方法@Delete("DELETE FROM users WHERE id = #{id}")void deleteById(@Param("id") long id);
}
/*UserService.java*/package xsl.service;@Component
public class UserService {final Logger logger = LoggerFactory.getLogger(getClass());@AutowiredUserMapper userMapper;......public User register(String email, String password, String name) {User user = new User();user.setName(name);user.setEmail(email);user.setPassword(password);user.setCreatedAt(System.currentTimeMillis());userMapper.insert(user);logger.info("user registered: {}", user.getEmail());return user;}......
}
/*UserController.java*/package xsl.web;@Controller
public class UserController {@AutowiredUserService userService;@GetMapping("/test")public ModelAndView test(){return new ModelAndView("test.html");}@GetMapping("/index")public ModelAndView index(HttpSession session) {User user = (User) session.getAttribute("__user__");Map<String, Object> model = new HashMap<>();if (user != null) {model.put("user", user);}return new ModelAndView("index.html", model);}@PostMapping("/register")public ModelAndView doRegister(@RequestParam("name") String name,@RequestParam("password") String password,@RequestParam("email") String email) {User user = userService.register(email, password, name);return new ModelAndView("redirect:/signin");}
}
下面为templates下的页面文件及其展示效果:
test.html
<html>
<body><ul><li>Coffee</li><li>Tea</li><li>Milk</li>
</ul>
</body>
</html>
index.html、_base.html
{% extends "_base.html" %}{% block main %}{% if user == null %}
<h3>Welcome!</h3>
<p><a href="/signin">Sign In</a> or <a href="/register">Register</a>
{% else %}
<h3>Welcome {{ user.name }}!</h3>
{% endif %}{% endblock %}
<!doctype html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><title>Spring Mvc Web App</title><script src="/static/js/jquery.js"></script><link href="/static/css/bootstrap.css" rel="stylesheet"></head><body style="font-size:16px"><div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm"><h5 class="my-0 mr-md-auto font-weight-normal"><a href="/">Home</a></h5><nav class="my-2 mr-md-3"><a class="p-2 text-dark" target="_blank" href="https://www.liaoxuefeng.com/wiki/1252599548343744">Learn</a></nav>{% if user==null %}<a href="/signin" class="btn btn-outline-primary">Sign In</a>{% else %}<span>Welcome, <a href="/user/profile">{{ user.name }}</a></span> <a href="/signout" class="btn btn-outline-primary">Sign Out</a>{% endif %}</div><div class="container" style="max-width: 960px"><div class="row"><div class="col-12 col-md">{% block main %}{% endblock %}</div></div><footer class="pt-4 my-md-5 pt-md-5 border-top"><div class="row"><div class="col-12 col-md"><h5>Copyright©2020 {{ __time__ }}</h5><p><a target="_blank" href="https://www.liaoxuefeng.com/">Learn</a> -<a target="_blank" href="https://www.liaoxuefeng.com/">Download</a> -<a target="_blank" href="https://github.com/michaelliao">Github</a></p></div></div></footer></div></body>
</html>
2、自动重启和打包
可以添加如下的依赖,然后我们修改代码后不用重启服务,Spring Boot应用可以自动重启。不起作用的话可以先设置Settings-Build-Compiler-勾选Build Project automatically,然后快捷键ctrl + shift + alt + /,选择Registry, 勾上 Compiler autoMake allow when app running。默认配置下,针对/static、/public和/templates目录中的文件修改,不会自动重启,因为禁用缓存后(yml配置文件里的pebble:cache为false),这些文件在IDEA里修改的话可以实时更新。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId>
</dependency>
如下所示,可以添加spring-boot-maven-plugin插件,这样使用Maven打包的时候创建的是包含所有依赖(包括内嵌的Tomcat)的jar(另外还会生成一个以.jar.original结尾的的只包含我们自己类的包),所以将其上传到服务器上后可以直接使用java -jar jar_name.jar命令来运行。
<build><finalName>awesome-app</finalName> <!-- 指定jar包名称 --><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.3.0.RELEASE</version> <!-- 与<parent>...</parent>中版本号一致 --></plugin></plugins></build>
使用spring-boot-maven-plugin插件打包Spring Boot应用的话,因为包含了所有依赖,所以生成的jar包会有几十兆,每次修改代码都上传这个大文件的话就有点麻烦。可以使用使用spring-boot-thin-launcher,如下所示修改pom.xml,这样生成的jar包只有几十K(只包含我们自己代码编译后的class以及一些其它信息),使用java -jar jar_name.jar命令运行这个jar包的话,会先在指定目录搜索看看依赖的jar包是否都存在,如果不存在,先从Maven中央仓库下载到本地,然后,再执行main()方法启动程序。
<build><finalName>awesome-app</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><dependencies><dependency><groupId>org.springframework.boot.experimental</groupId><artifactId>spring-boot-thin-layout</artifactId><version>1.0.27.RELEASE</version></dependency></dependencies></plugin></plugins></build>
spring-boot-thin-launcher在启动时搜索的依赖库默认目录是用户主目录的.m2
,我们也可以指定下载目录,例如,将下载目录指定为当前目录:java -D thin.root=. -jar jar_name.jar,这样当前目录下就会生成一个repository目录来存放依赖包。也可以专门用来下载依赖项而不执行程序:java -D thin.dryrun=true -D thin.root=. -jar jar_name.jar,这称之为“预热”(warm up)。如果服务器不允许从外网下载文件,那么可以在本地预热,然后把repository目录上传到服务器,只要依赖项没有变化,后续改动只需要上传jar包即可。
3、使用Actuator进行监控
前面我们已经介绍了使用JMX对Java应用程序包括JVM进行监控,Spring Boot已经内置了一个监控功能,它叫Actuator。使用Actuator只需添加如下依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Actuator会把它能收集到的所有信息都暴露给JMX,然后可以通过URL/actuator/挂载一些监控点,例如,输入http://localhost:8080/actuator/health可以查看应用程序的当前状态, 如下所示:
{"status": "UP"
}
Actuator默认把所有访问点暴露给JMX,但出于安全原因,只有health和info会暴露给Web,其它的访问点还有 beans,、env等。要暴露更多的访问点给Web,需要在application.yml中加上配置:
management:endpoints:web:exposure:include: info, health, beans, env
许多网关作为反向代理需要一个URL来探测后端集群应用是否存活,这个URL就可以提供给网关使用。
4、使用Profiles
前面讲过Spring提供的Profiles功能,Profile表示一个环境的概念,如开发、测试和生产这3个环境,或者按git分支定义master、dev这些环境,Spring Boot对Profiles的支持在于,可以在application.yml中为每个环境进行配置。如下所示,分隔符---下面分别是test环境和production的配置,算上默认的环境default(启动应用的时候不指定任何Profile),这里面一共配置了三个环境:默认使用8080端口,在test环境下的话使用8000端口,在production环境下使用80端口并且启用Pebble的缓存。要以test环境启动程序的话,可以为:java -D spring.profiles.active=test -jar jar_name.jar。
spring:application:name: ${APP_NAME:unnamed}datasource:......pebble:suffix:cache: falseserver:port: ${APP_PORT:8080}---spring:profiles: testserver:port: 8000---spring:profiles: productionserver:port: 80pebble:cache: true
如下所示,默认情况下in成员会使用Foo类注入,如果使用其它环境的话就使用Bar注入:
public interface MyInter {...
}@Component
@Profile("default")
public class Foo implements MyInter{...
}@Component
@Profile("!default")
public class Bar implements MyInter{...
}@Autowired
private MyInter in;
5、条件注解
在Spring中可以通过@Conditional来使指定的类生效(能够通过Ioc实例化该类),Spring Boot中有更多的Conditional:
@ConditionalOnProperty:如果有指定的配置,条件生效;
@ConditionalOnBean:如果有指定的Bean,条件生效;
@ConditionalOnMissingBean:如果没有指定的Bean,条件生效;
@ConditionalOnMissingClass:如果有指定的Class,条件生效;
@ConditionalOnMissingClass:如果没有指定的Class,条件生效;
@ConditionalOnWebApplication:在Web环境中条件生效;
@ConditionalOnExpression:根据表达式判断条件是否生效。
@Component
@ConditionalOnProperty(name="app.smtp", havingValue="true") //配置文件中存在app.smtp=true,IoC才会实例化MailService,否则抛出异常
public class MailService {...
}@Component
@ConditionalOnClass(name = "javax.mail.Transport") //classpath中存在类javax.mail.Transport,IoC才会实例化MailService,否则抛出异常
public class MailService {...
}
如果需要注入的是一个接口成员的话,IoC会自动搜索接口的实现类来注入该成员,如果有多个接口实现类的话,可以通过条件注解来设置使用哪个类,比如使用@ConditionalOnProperty来设置通过配置文件中配置来指定使用哪个类:
@Component
@ConditionalOnProperty(name = "app.storage", havingValue = "file", matchIfMissing = true)//配置文件存在app.storage且其值为file,或者配置文件中不存在app.storage,该类有效
public class FileUploader implements Uploader {...
}@Component
@ConditionalOnProperty(name = "app.storage", havingValue = "s3") //配置文件存在app.storage,且其值为s3时,该类有效
public class S3Uploader implements Uploader {...
}@Component
public class UserImageService {@AutowiredUploader uploader; //通过配置文件中配置来选择使用哪个接口实现类来注入该成员
}@Configuration
@ComponentScan
public class App {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(App.class);UserImageService ser = context.getBean(UserImageService.class);}
}
6、读取配置文件
如下所示的application.yml配置文件,除了使用@Value来读取一个配置信息到成员中去,如@Value("${storage.local.max-size:102400}"),还可以将配置文件中的一组信息保存到JavaBean中来读取,比如我们将storage.local中的信息保存到StorageConfiguration中来使用:
storage:local:root-dir: ${STORAGE_LOCAL_ROOT:/var/storage}max-size: ${STORAGE_LOCAL_MAX_SIZE:102400}allow-empty: falseallow-types: jpg, png, gif
@Configuration //相当于@Component,也可以不写该注解,然后在@SpringBootApplication入口类中使用@Import(StorageConfiguration.class)来手动加载
@ConfigurationProperties("storage.local") //读取配置文件中storage.local的信息到本类
public class StorageConfiguration {private String rootDir;private int maxSize;private boolean allowEmpty;private List<String> allowTypes;String getRootDir(){...}...
}@Component
public class StorageService {@AutowiredStorageConfiguration storageConfig;@PostConstructpublic void init() {storageConfig.getRootDir();storageConfig.getMaxSize();...}
}
7、禁用自动配置
Spring Boot大量使用自动配置和默认配置,极大地减少了代码,通常只需要加上几个注解,并对配置文件配置以下即可。例如,配置JDBC,默认情况下,只需要如下所示配置一个spring.datasource,Spring Boot就会自动创建出DataSource、JdbcTemplate、DataSourceTransactionManager,非常方便。
spring:datasource:url: jdbc:hsqldb:file:testdbusername: sapassword:dirver-class-name: org.hsqldb.jdbc.JDBCDriver
有时候,我们需要要禁用某些自动配置,比如系统有主从两个数据库,而Spring Boot的自动配置只能配一个,这时候就需要关闭数据库的自动配置。关于怎样使SpringBoot支持两个数据库,具体可以参考:禁用自动配置 - 廖雪峰的官方网站。
8、添加Filter
在SpringBoot中想要使用Filter的话,通过继承FilterRegistrationBean来实现,如下所示的AuthFilterRegistrationBean实现了过滤所有所有URL的Filter:
@Component
public class AuthFilterRegistrationBean extends FilterRegistrationBean<Filter> {@AutowiredUserService userService;@Overridepublic Filter getFilter() { //返回要注册的FiltersetOrder(10); //设置Filter的顺序return new AuthFilter();}class AuthFilter implements Filter {public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{userService.func(); //内部类可以直接使用外部类的成员...}}
}
上面对所有请求都会过滤,也可以设置仅对指定路径有效:
@Component
public class ApiFilterRegistrationBean extends FilterRegistrationBean<Filter> {@PostConstructpublic void init() {setOrder(20);setFilter(new ApiFilter()); //设置要注册的FiltersetUrlPatterns(List.of("/api/*")); //设置要过滤的路径}class ApiFilter implements Filter {...}
}