2019/9/19 创建,最近更新于 2019/9/24

基础知识

1. Vue请求SpringBoot出现跨域

【后端解决】

  • 方式一:Application.java直接配置Bean```java @SpringBootApplication public class Application { public static void main(String[] args) {
    1. SpringApplication.run(Application.class, args);
    }
  1. @Bean
  2. public CorsFilter corsFilter() {
  3. final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  4. final CorsConfiguration config = new CorsConfiguration();
  5. // 允许cookies跨域
  6. config.setAllowCredentials(true);
  7. // 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080
  8. // ,以降低安全风险。。
  9. config.addAllowedOrigin("*");
  10. // 允许访问的头信息,*表示全部
  11. config.addAllowedHeader("*");
  12. // 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
  13. config.setMaxAge(18000L);
  14. // 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等
  15. config.addAllowedMethod("*");
  16. /*
  17. * config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET");//
  18. * 允许Get的请求方法 config.addAllowedMethod("PUT");
  19. * config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE");
  20. * config.addAllowedMethod("PATCH");
  21. */
  22. source.registerCorsConfiguration("/**", config);
  23. return new CorsFilter(source);
  24. }

}


- 方式二:定义配置类<br />新建一个`CorsConfig.java`(名字随意)```java
@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {

    //跨域处理 另一种方式
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //设置允许跨域的路径
        registry.addMapping("/**")
                //设置允许跨域请求的域名
                .allowedOrigins("*")
                //是否允许证书 不再默认开启
                .allowCredentials(true)
                //设置允许的方法
                .allowedMethods("*")
                //跨域允许时间
                .maxAge(3600);
    }
}
  • 方式三
    在需要跨域的Controller类或方法名上添加@CrossOrigin注解,其中@CrossOrigin中的2个参数:
    • origins : 允许访问的域列表。如(origins = "http://csdn.com")
    • maxAge:响应前缓存持续最大时间(秒)。如(maxAge = 600)

注意,则不是全局解决方案。

2. 出现XML解析错误:找不到根元素

主要是由于SpringBoot返回了null值造成的,在controller方法里面一定要返回一个非null值

重点应用

1. SpringBoot连接MySQL/MariaDB

要注意看导入的jar包mysql-connector-java的版本,分为6.0以下6.0及以上

  • 6.0及以下
    对应 com.mysql.jdbc.Driver,按照普通方式年连就可以了。
  • 6.0及以上
    对应com.mysql.cj.jdbc.Driver,设置数据库连接的时候必须要设置serverTimezone的时区,推荐设置为```properties

    北京时间东八区

    serverTimezone=GMT%2B8

    或者使用上海时间

    serverTimezone=Asia/Shanghai
    <br />完整propeties文件如下:```properties
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.url=jdbc:mysql://localhost:3306/jdbc?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    

    其中&useUnicode=true&characterEncoding=utf8&useSSL=false不是必须,但推荐加上。


yaml文件同理。

2. SpringBoot 2.x启动时数据库脚本自动执行

需要在配置文件中设置,文件名必须是schema-*.sqldata-*.sql

spring.datasource.initialization-mode: always

如果想使用自定义文件名,需要修改配置文件,新增一行:

spring.datasource.schema=classpath:department.sql

如果有多个,直接在后面用逗号分隔,例如: spring.datasource.schema=classpath:department.sql,classpath:department1.sql

3. SpringBoot使用Druid数据源

先通过maven导入数据源(顺便导入log4j)

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.8</version>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

resources\下增加log4j的配置文件log4j.properties:

### set log levels ###
log4j.rootLogger = debug,stdout

### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =  %d{ABSOLUTE} %5p %c{1}:%L - %m%n

在配置文件中增加以下属性(在spring.datasource下)

# 数据源其他配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

增加一个配置类config/DruidConfig.java:

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druid() {
        return new DruidDataSource();
    }

    // 配置监控
    // 1. 配置管理后台的 Servlet
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "123456");
        initParams.put("allow", ""); // 允许所有
        initParams.put("deny", "192.168.137.1");
        bean.setInitParameters(initParams);
        return bean;
    }

    // 2. 配置 web监控的 filter
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js, *.css, /druid/*");
        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}

然后就可以通过localhost:8080/druid来登录管理界面

4. SpringBoot使用MyBatis

mybatis详细内容参考:MyBatis学习笔记

通过maven导入mybatis

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>

4.1 注解版

再新建操作数据库的mapper,mapper/DepartmentMapper.java:

@Mapper
public interface DepartmentMapper {

    @Select("select * from department where id=#{id}")
    public Department getDeptById(Integer id);

    @Delete("delete from department where id=#{id}")
    public int deleteDeptById(Integer id);

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into department(departmentName) values(#{departmentName})")
    public int insertDept(Department department);

    @Update("update department set departmentName=#{departmentName} where id=#{id}")
    public int updateDept(Department department);
}

mybatis 可以使用@Insert(replace into xxx)

新建一个controller进行测试,controller/DeptController.java:

@RestController
public class DeptController {

    @Autowired
    DepartmentMapper departmentMapper;

    @GetMapping("/dept/{id}")
    public Department getDepartment(@PathVariable("id") Integer id) {
        return departmentMapper.getDeptById(id);
    }

    @GetMapping("/dept")
    public Department insertDept(Department department) {
        departmentMapper.insertDept(department);
        return department;
    }
}

4.2 自定义mybatis配置

新建文件config/MyBatisConfig.java:

@org.springframework.context.annotation.Configuration
public class MyBatisConfig {

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return new ConfigurationCustomizer(){
            @Override
            public void customize(Configuration configuration) {
                // 开启驼峰命名映射规则
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

如果mapper文件特别多的情况下,可以在MyBatisConfig.java或者SpringBootApplication.java类上方添加注解,value值对应要添加mapper的包名:

@MapperScan(value = "com.atguigu.springboot.mapper")
@SpringBootApplication
public class SpringBoot06DataMybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBoot06DataMybatisApplication.class, args);
    }

}

4.3 xml版

首先在application.properties中指定mybatis-config文件和mapper文件的位置:

mybatis.config-location: classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations: classpath:mybatis/mapper/*.xml

新建mybatis/mybatis-config.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

再新建mapper,mapper/EmployeeMapper.java:

@Mapper
public interface EmployeeMapper {

    public Employee getEmpById(Integer id);

    public void insertEmp(Employee employee);
}

建立对应的mapper配置文件,mapper/EmployeeMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.springboot.mapper.EmployeeMapper">
    <!--
        public Employee getEmpById(Integer id);
        public void insertEmp(Employee employee);
    -->

    <select id="getEmpById" resultType="com.atguigu.springboot.bean.Employee">
        SELECT * FROM employee WHERE id=#{id}
    </select>

    <insert id="insertEmp">
        INSERT INTO employee(lastName, email, gender, d_id) VALUES (#{lastName},#{email},#{gender},#{d_id)})
    </insert>
</mapper>

5. SpringBoot使用Spring Data JPA

首先导入maven依赖,编辑配置文件:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/jpa?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      # 更新或者创建数据表
      ddl-auto: update
    # 显示sql
    show-sql: true

编写实体类 entity/User.java

@Entity // 告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tbl_user") // 指定对应表名,默认与类名相同(小写)
public class User {

    @Id // 这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增
    private Integer id;

    @Column(name = "last_name", length = 50) // 对应数据表列
    private String lastName;

    @Column // 默认列名就是属性名
    private String email;

    // 省略getter/setter
}

编写Dao接口操作实体类 repository/UserRepository.java

// 继承 JpaRepository 来完成对数据库的操作
public interface UserRepository extends JpaRepository<User, Integer> {

}

然后就可以直接使用了,在这里新建一个文件controller/UserController.java

@RestController
public class UserController {

    @Autowired
    UserRepository userRepository;

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Integer id) {
        User user = userRepository.findById(id).get();
        return user;
    }

    @GetMapping("/user")
    public User insertUser(User user) {
        User save = userRepository.save(user);
        return save;
    }
}

6. SpringBoot与缓存

使用缓存将最常用的的数据缓存到内存中,避免频繁操作数据库

6.1 使用默认Cache

导入cache依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

在启动类上加上注解@EnableCaching开启缓存

@CacheConfig :设置公共配置,放在类上

重要的3个注解(放在调用的方法上)

注解 功能 使用场景
@Cacheable 能够根据方法的请求参数对其结果进行缓存 查询操作
@CacheEvict 清空缓存 删除操作
@CachePut 保证方法被调用,又希望结果被缓存。 更新/增加

常用注解属性

注解 描述 拥有者
cacheNames/value 指定定缓存的名字,即缓存块。可以是一个数组 all
key 缓存数据时的key,默认是使用方法参数的值,可以使用SpEL表达式 all
keyGenerator 缓存的生成策略,和key二选一,都是生成键的,keyGenerator可自定义 all
cacheManager 指定是缓存管理器,默认是ConcurrentHashMap(导入redis后会自动替换) all
condition 指定缓存的条件(满足什么条件时才缓存),可用SpEL表达式(如#id>0,表示当入参id大于0时才缓存) all
unless 否定缓存,即满足unless指定的条件时,方法的结果不进行缓存,使用unless时可以在调用的方法获取到结果之后再进行判断(如 #result==null,表示如果结果为null时不缓存)。 all
sync 是否使用异步模式进行缓存 all
allEntries 是否清除指定缓存中的所有键值对,默认为false,设置为true时会清除缓存中的所有键值对,与key属性二选一使用 @CacheEvict
beforeInvocation 在@CacheEvict注解的方法调用之前清除指定缓存,默认为false,即在方法调用之后清除缓存。设置为true时则会在方法调用之前清除缓存(在方法调用之前还是之后清除缓存的区别在于方法调用时是否会出现异常,若不出现异常,这两种设置没有区别,若出现异常,设置为在方法调用之后清除缓存将不起作用,因为方法调用失败了) @CacheEvict

注:①既满足condition又满足unless条件的也不进行缓存
②使用异步模式进行缓存时(sync=true):unless条件将不被支持

@Caching

是@Cacheable、@CachePut、@CacheEvict的组合,定义复杂的缓存规则,在这个组合中只要有@CachePut就一定会调用被注解的方法

使用示例

@Cacheable(cacheNames = "emp", condition = "#a0>1", unless = "#result == null")
public Employee getEmp(Integer id) {
    //...
}
@CachePut(cacheNames = "emp", key = "#employee.id")
public Employee updateEmp(Employee employee) {
    //...
}
@Caching(
    cacheable = {@Cacheable(value = "emp", key = "#lastName")},
    put = {@CachePut(value = "emp", key = "#result.id"),
           @CachePut(value = "emp", key = "#result.email")})
public Employee getEmpByLastName(String lastName) {
    //...
}

注意,@Cacheable、@CachePut、@Caching使用的key要一致,否则相同的数据在缓存中的key不是同一个

6.2 Redis基本使用

导入redis依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

增加配置

spring.redis.host=localhost
# 指定是哪个数据库(0~15,共16个)
spring.redis.database=0
# redis没有用户名,默认无密码

在需要使用的类下声明:

@Autowired
StringRedisTemplate stringRedisTemplate; // 常用,k-v都为字符串

@Autowired
RedisTemplate redisTemplate; // k-v都为对象

然后就可以直接运行:

// stringRedisTemplate.opsForValue().xxx; // 操作字符串
// stringRedisTemplate.opsForList().xxx;  // 操作列表
// stringRedisTemplate.opsForHash().xxx;  // 操作哈希表
// stringRedisTemplate.opsForSet().xxx;   // 操作集合
// stringRedisTemplate.opsForZSet().xxx;  // 操作有序集合
// ......
// 例如
stringRedisTemplate.opsForValue().append("msg", "hello");
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);

直接保存对象需要序列化对象,但是会以二进制保存信息,不够直观,推荐转换成json数据存入redis中。

序列化对象: 使用JDK内置的序列化方法时,需要在对应的bean实现Serializable接口

public class Employee implements Serializable {
    ...
}

6.3 使用Jackson2JsonRedisSerializer序列化对象【不推荐】

新建一个config类config/MyRedisConfig.java:

@Configuration
public class MyRedisConfig {

    /**
     * 这里的泛型Employee是你想序列化的对象
     */
    @Bean
    public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        /*使用Jackson2JsonRedisSerializer复杂类型会报错*/
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);

        template.setDefaultSerializer(serializer);
        return template;
    }
}

注意,使用Jackson2JsonRedisSerializer复杂类型会报错,且Jackson2JsonRedisSerializer需要指明序列化的类Class,这代表一个实体类就有一个RedisCacheManager,代码冗余。

再在需要使用的地方注入:

@Autowired
RedisTemplate<Object, Employee> empRedisTemplate;

然后在需要序列化的地方修改原来的代码即可:

// redisTemplate.opsForValue().set("emp-01", employee);
// 修改为
empRedisTemplate.opsForValue().set("emp-01", employee);

6.4 使用fastjson序列化对象【推荐】

导入fastjson依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.57</version>
</dependency>

修改MyRedisConfig.java代码:

@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate redisTemplate = new RedisTemplate();
    redisTemplate.setConnectionFactory(redisConnectionFactory);

    GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
    redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);//设置默认的Serialize,包含 keySerializer & valueSerializer

    //redisTemplate.setKeySerializer(fastJsonRedisSerializer);//单独设置keySerializer
    //redisTemplate.setValueSerializer(fastJsonRedisSerializer);//单独设置valueSerializer
    return redisTemplate;
}

如果需要定制化,可以使用FastJsonRedisSerializer来代替GenericFastJsonRedisSerializer

在需要使用的地方注入:

@Autowired
RedisTemplate redisTemplate; // 如果之前没有注入的话

然后在需要序列化对象的地方使用:

redisTemplate.opsForValue().set("emp-01", employee);

序列化后的结果(存入数据库中的结果) {“@type”:”com.atguigu.cache.bean.Employee”,”dId”:1,”email”:”ls@qq.com“,”gender”:1,”id”:1,”lastName”:”李四”} 会加上@type字段用于指明是哪个类,以便正确的反序列化

在普通代码中使用fastjson 先导入包import com.alibaba.fastjson.JSON;,再执行

Employee employee = employeeMapper.getEmpById(1);
String jsonString = JSON.toJSONString(employee);
System.out.println(jsonString);
Employee emp = JSON.parseObject(jsonString, Employee.class);
System.out.println(emp);

注意,此时序列化后的结果不带@type属性,反序列化的时候要指定目标对象的类型

6.4 自定义CacheManager(使用fastjson)

手动调用缓存时不会经过cacheManager,自动调用缓存时会调用cacheManager

导入redis依赖后,cache会默认保存到redis中,但是是通过jdk序列化保存的,需要配置。

MyRedisConfig.java中增加方法:

@Bean(name = "fastjsonRedis")
@Primary // 加上之后如果没有指定cacheManager,则默认使用该缓存管理器(只能在其中一个cacheManager上指定)
public CacheManager fastjsonRedis(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
        /*.entryTtl(Duration.ofSeconds(30)) //设置过期时间*/
        .disableCachingNullValues() //禁止缓存null对象
        .computePrefixWith(cacheName -> "yourAppName".concat(":").concat(cacheName).concat(":")) //此处定义了cache key的前缀,避免公司不同项目之间的key名称冲突
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //定义了key和value的序列化协议,同时hash key和hash value也被定义
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));

    return RedisCacheManager.builder(redisConnectionFactory)
        .cacheDefaults(cacheConfiguration)
        .build();
}

配置多cacheManager类似,只需要指定@Bean(name = "xxxx"),且只能在其中一个上面添加@Primary,用的时候指定对应的cacheManager即可。

7. SpringBoot与消息

使用消息将实时性不强的操作放到队列中,提高效率。 例如,用户注册场景。当用户在网页上提交注册信息之后,将用户注册信息放入到消息队列中,然后立即返回给用户注册成功的信息。服务器再从消息队列中将用户信息取出来存入到数据库中、发送确认邮件等。 注意,前置验证码短信/邮件属于实时性强的操作

7.1 安装RabbitMQ

推荐安装带management标志的,有管理界面

虚拟机安装

如果虚拟机能联网,能ping通本地物理机,但是本地物理机ping不通虚拟机,说明网卡设置有问题。

  1. 虚拟机选择nat模式(VMnet8),然后将nat设置中的网关设置成 xx.xx.xx.1(例如192.168.137.1)
  2. 本地主机编辑VMnet8网卡设置,选择手动指定IP,将IP设置成xx.xx.xx.2,网关设置为xx.xx.xx.1 以上均为同一网段。

运行命令

# 下载
sudo docker pull rabbitmq:3.8-rc-management
# 运行(5672是通信端口,15672是管理页面端口)
sudo docker run -d -p 5672:5672 -p 15672:15672 --name=myrabbitmq rabbitmq:3.8-rc-management
# (关闭后)启动
sudo docker start myrabbitmq
# 停止
sudo docker stop myrabbitmq

物理机安装

7.2 使用RabbitMQ

进入web管理界面192.168.137.135:15672,默认用户名和密码是guest

添加exchanges,queues,然后在exchange中通过routerKey绑定绑定queue

7.3 SpringBoot整合RabbitMQ

首先添加amqp依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

在配置文件中添加内容

spring.rabbitmq.host=192.168.137.136
spring.rabbitmq.username=guest
spring.redis.password=guest
# 端口默认是5672
spring.rabbitmq.port=5672

在需要使用的地方注入

@Autowired
RabbitTemplate rabbitTemplate;

使用

/**
 * 1. 单播(点对点)
 */
@Test
public void direct() {
    // 需要直接构造一个message,定义消息体和消息头
    // rabbitTemplate.send(exchange, routeKey, message);

    // 自动将object转换成消息
    // rabbitTemplate.convertAndSend(exchange, routeKey, object);

    // Map<String, Object> map = new HashMap<>();
    // map.put("msg", "这是第二个消息");
    // map.put("data", Arrays.asList("helloworld",123,true));
    // rabbitTemplate.convertAndSend("exchange.direct", "atguigu.news", map);
    Book book = new Book("西游记", "章承恩");
    rabbitTemplate.convertAndSend("exchange.direct", "atguigu.news", book);
}

/**
 * 广播
 */
@Test
public void fanout(){
    rabbitTemplate.convertAndSend("exchange.fanout", "",new Book("三国演义", "罗贯中"));
}

/**
 * 接收消息
 */
@Test
public void receive() {
    Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
    System.out.println(o.getClass());
    System.out.println(o);
}

修改序列化方式为json

增加配置类config/MyAMQPConfig.java

@Configuration
public class MyAMQPConfig {

    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

注意导入的MessageConverter是org.springframework.amqp.support.converter.MessageConverter

7.4 SpringBoot监听消息队列

在启动类上添加@EnableRabbit开启监听

新建一个service类service/BookService.java

@Service
public class BookService {

    @RabbitListener(queues = "atguigu.news")
    public void receive(Book book) {
        System.out.println("收到消息," + book);
    }

    @RabbitListener(queues = "atguigu")
    public void receive02(Message message) { // org.springframework.amqp.core.Message;
        System.out.println(message.getBody());
        System.out.println(message.getMessageProperties());
    }
}

7.5 使用AmqpAdmin管理系统

可以创建和删除 Queue,Exchange,Binding。

在需要使用的地方注入:

@Autowired
AmqpAdmin amqpAdmin;

使用

@Test
public void createExchange() {
    amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
    amqpAdmin.declareQueue(new Queue("amqpadmin.queue", true));
    amqpAdmin.declareBinding(new Binding("amqpadmin.queue", Binding.DestinationType.QUEUE, "amqpadmin.exchange", "amqp.haha",null));
    System.out.println("创建完成");
}

8. SpringBoot与检索

8.1 安装ElasticSearch

虚拟机安装

sudo docker pull elasticsearch:6.4.0

注意:elasticsearch与Spring Data Elasticsearch的版本一定要匹配,否则会获取不到节点 这里Spring Data Elasticsearch的版本是3.1.10

运行命令

sudo docker network create esnetwork

sudo docker run -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -e "discovery.type=single-node" -d -p 9200:9200 -p 9300:9300 --name=ES01 --net=esnetwork elasticsearch:6.4.0

8.2 使用ElasticSearch

打开浏览器,访问xx.xx.xx.xx:9200,出现json数据响应说明安装成功。

参见Elasticsearch: 权威指南

8.3 SpringBoot整合ElasticSearch

SpringBoot默认支持2种技术来和ES交互:

  1. Jest(默认不生效)
    需要导入jest工具包(io.searchbox.client.JestClient)
  2. Spring Data ElasticSearch

首先导入elasticsearch依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

使用Jest操作

首先注释掉spring-boot-starter-data-elasticsearch,导入Jest

<!-- https://mvnrepository.com/artifact/io.searchbox/jest -->
<dependency>
    <groupId>io.searchbox</groupId>
    <artifactId>jest</artifactId>
    <version>6.3.1</version>
</dependency>

增加配置

spring.elasticsearch.jest.uris=http://192.168.137.136:9200

在需要使用的地方先注入JestClient

@Autowired
JestClient jestClient;

然后使用

// 1. 给ES中索引(保存)一个文档
@Test
public void contextLoads() {
    Article article = new Article();
    article.setId(1);
    article.setTitle("好消息");
    article.setAuthor("张三");
    article.setContent("Hello World");

    // 构建一个索引
    Index index = new Index.Builder(article).index("atguigu").type("news").build();
    try {
        // 执行
        jestClient.execute(index);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 2. 搜索
@Test
public void search() {
    // 构建查询表达式
    String json = "{\n" +
        "    \"query\" : {\n" +
        "        \"match\" : {\n" +
        "            \"content\" : \"Hello\"\n" +
        "        }\n" +
        "    }\n" +
        "}";
    // 构建搜索
    Search serch = new Search.Builder(json).addIndex("atguigu").addType("news").build();

    // 执行
    try {
        SearchResult result = jestClient.execute(serch);
        System.out.println(result.getJsonString());
    } catch (IOException e) {
        e.printStackTrace();
    }

}

注意,bean中如果有主键要用@JestId标注主键

使用Spring Data ElasticSearch操作

解除elasticsearch的注释,增加配置

spring.data.elasticsearch.cluster-name=docker-cluster
spring.data.elasticsearch.cluster-nodes=192.168.137.136:9300

新建一个repository/BookRepository.java

public interface BookRepository extends ElasticsearchRepository<Book, Integer> {
}

注意,在对应的Bean的类上加上注解@Document(indexName = "atguigu", type = "book")指定储存的索引和类型

在需要使用的地方注入BookRepository

@Autowired
BookRepository bookRepository;

然后使用

@Test
public void test02() {
    Book book = new Book(1,"西游记","吴承恩");
    bookRepository.index(book);
}

还可以在接口中自定义方法(不用实现):

public interface BookRepository extends ElasticsearchRepository<Book, Integer> {

    // 自定义方法
    public List<Book> findByBookNameLike(String bookName);
}

方法命名参考:官方文档

然后使用自定义方法:

@Test
public void test03() {
    for (Book book : bookRepository.findByBookNameLike("游")) {
        System.out.println(book);
    }
}

9. SpringBoot与任务

9.1 异步任务

在启动类上增加注解@EnableAsync开启异步,新建一个Service类service/AsyncService.java

@Service
public class AsyncService {

    @Async // 指名是异步方法
    public void hello() {
        // 模拟处理其他任务
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("处理任务中。。。");
    }
}

新建一个Controller类controller/AsyncController.java

@RestController
public class AsyncController {

    @Autowired
    AsyncService asyncService;

    @GetMapping("/hello")
    public String hello() {
        asyncService.hello();
        return "success";
    }
}

启动并在浏览器访问,即可看到效果。

9.2 定时任务

在启动类上增加注解@EnableScheduling开启异步,新建一个Service类service/ScheduledService.java

@Service
public class ScheduledService {

    /**
     * cron = "秒 分 时 日 月 星期几"
     * "0 0/5 14,18 * * ?"        每天14点、18点,每隔5分钟执行一次
     * “0 15 10 ? * 1-6”       每个月的周一到周六10:15执行一次
     * “0 0 2 LW * ?”          每个月的最后一个工作日凌晨2点执行一次
     * “0 0 2-4 ? * 1#1”       每个月的第一个周一凌晨2点到4点,每个整点执行一次
     */
    @Scheduled(cron = "0 * * * * MON-SAT")
    public void hello() {
        System.out.println("hello ...");
    }
}

9.3 邮件任务

引入mail依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

增加配置内容:

spring.mail.host=smtp.qq.com
spring.mail.username=loveshes@qq.com
# 授权码
spring.mail.password=ndvkwgixrsigfide
# 开启SSL
spring.mail.properties.mail.smtp.sll.enable=true

在需要使用的地方自动注入:

@Autowired
JavaMailSenderImpl mailSender;

使用:

@Test
public void simpleMail() {
    SimpleMailMessage message = new SimpleMailMessage();
    message.setSubject("通知-今晚开会");
    message.setText("今晚7:30开会,别迟到哦");

    message.setTo("wangzhe.ncu@qq.com");
    // 一定要设置,并且要与配置中的用户相同(可以是别名,但是要求为同一个账户)
    message.setFrom("loveshes@qq.com"); 

    mailSender.send(message);
}

@Test
public void fileMail() throws MessagingException {
    MimeMessage mimeMessage = mailSender. createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

    helper.setSubject("通知-今晚开会");
    helper.setText("<b style='color:red'>今晚7:30开会</b>", true); // 指定true用于发送html格式内容

    helper.setTo("wangzhe.ncu@qq.com");
    helper.setFrom("loveshes@qq.com");

    helper.addAttachment("1.jpg", new File("D:\\Media\\图片\\欣小萌壁纸\\欣小萌_16.jpg"));

    mailSender.send(mimeMessage);
}

10. Spring与安全

10.1 使用Spring Security

导入security依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

新建一个security配置类config/MySecurityConfig.java

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // super.configure(http);
        // 定制请求的授权规则
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasAnyRole("VIP1")
                .antMatchers("/level2/**").hasAnyRole("VIP2")
                .antMatchers("/level3/**").hasAnyRole("VIP3");

        // 开启自动配置的登陆功能,没登录会用security内置的登陆页
        http.formLogin();
        // 1. /login 来到登录页
        // 2. 重定向到 /login?error 表示登录失败
        // 3. 更多详细规定

        // 开启自动配置的注销功能
        http.logout().logoutSuccessUrl("/");

        // 开启记住我功能
        http.rememberMe();
    }

    // 定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // super.configure(auth);
        // 注意,spring security 5.0 要指定密码加密方式
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456"))
                .roles("VIP1", "VIP2")
                .and()
                .withUser("lisi").password(new BCryptPasswordEncoder().encode("123456"))
                .roles("VIP2", "VIP3")
                .and()
                .withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456"))
                .roles("VIP1", "VIP3");

    }
}

新建一个controller类controller/KungFuController.java

@Controller
public class KungFuController {

    private final String PREFIX = "pages/";

    @GetMapping("/")
    public String index() {
        return "welcome";
    }

    @GetMapping("/userlogin")
    public String loginPage() {
        return PREFIX + "login";
    }

    @GetMapping("level{index}/{path}")
    public String level1(@PathVariable("index") String index, @PathVariable("path") String path) {
        return PREFIX + "level" + index + "/" + path;
    }

}

修改html文件使用注销功能:

<form th:action="@{/logout}" method="post">
    <input type="submit" value="注销"/>
</form>

10.2 Security整合Thymeleaf

SpringBoot 2.1.x 不能正常显示,建议使用2.0.7,修改spring-boot-starter-parent的版本为2.0.7 不用修改配置文件

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">欢迎光临武林秘籍管理系统</h1>
<div sec:authorize="!isAuthenticated()">
    <h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/userlogin}">请登录</a></h2>
</div>
<div sec:authorize="isAuthenticated()">
    <h2><span sec:authentication="name"></span>,您好,您的角色有:
        <span sec:authentication="principal.authorities"></span></h2>
    <form th:action="@{/logout}" method="post">
        <input type="submit" value="注销"/>
    </form>
</div>

<hr>

<!-- 有VIP1角色才显示 -->
<div sec:authorize="hasRole('VIP1')">
    <h3>普通武功秘籍</h3>
    <ul>
        <li><a th:href="@{/level1/1}">罗汉拳</a></li>
        <li><a th:href="@{/level1/2}">武当长拳</a></li>
        <li><a th:href="@{/level1/3}">全真剑法</a></li>
    </ul>
</div>

<div sec:authorize="hasRole('VIP2')">
    <h3>高级武功秘籍</h3>
    <ul>
        <li><a th:href="@{/level2/1}">太极拳</a></li>
        <li><a th:href="@{/level2/2}">七伤拳</a></li>
        <li><a th:href="@{/level2/3}">梯云纵</a></li>
    </ul>
</div>

<div sec:authorize="hasRole('VIP3')">
    <h3>绝世武功秘籍</h3>
    <ul>
        <li><a th:href="@{/level3/1}">葵花宝典</a></li>
        <li><a th:href="@{/level3/2}">龟派气功</a></li>
        <li><a th:href="@{/level3/3}">独孤九剑</a></li>
    </ul>
</div>

</body>
</html>

10.3 自定义登录页

修改config/MySecurityConfig.java

http.formLogin().loginPage("/userlogin"); // 指定登录页面

增加登录页面pages/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h1 align="center">欢迎登陆武林秘籍管理系统</h1>
    <hr>
    <div align="center">
        <form th:action="@{/userlogin}" method="post">
            用户名:<input name="user"/><br>
            密码:<input name="pwd"><br/>
            <input type="checkbox" name="remember"> 记住我<br/>
            <input type="submit" value="登陆">
        </form>
    </div>
</body>
</html>

修改config/MySecurityConfig.java

http.formLogin().loginPage("/userlogin").usernameParameter("user").passwordParameter("pwd");

http.rememberMe().rememberMeParameter("remember");

10.4 使用Shiro

参考 Shiro学习笔记

11. SpringBoot与分布式

11.1 zookeeper + dubbo

安装 zookeeper + dubbo

sudo docker pull zookeeper
sudo docker run --name zk01 -p 2181:2181 --restart always -d zookeeper

引入依赖:

12. SpringBoot与开发热部署

添加spring-boot-devtools

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

以后会自动热刷新。如果不生效可以按Ctrl+ F9编译文件。

13. SpringBoot与监管

13.1 基本使用

参考Spring Boot 2.0官方文档之 Actuator

引入actuator依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

修改配置文件:

# 默认只有 health 和 info,使用 * 开启所有节点
management.endpoints.web.exposure.include=*

info节点显示的信息来源于 application.properties 中的 info和git.properties中的内容:

# application.properties
info.app.id=hello
info.app.version=E1.0
# git.properties
git.branch=master
git.commit.id=xhjakihd
git.commit.time=2019-12-12 12:12:12

访问http://localhost:8080/actuator/info显示的信息为:

{
  "app": {
    "id": "hello",
    "version": "1.0"
  },
  "git": {
    "commit": {
      "time": "2019-12-12 12:12:12",
      "id": "xhjakih"
    },
    "branch": "master"
  }
}

远程关闭,增加配置:

management.endpoint.shutdown.enabled=true

发送post请求到localhost:8080/actuator/shutdown远程关闭应用

13.2 定制端点

13.3 安全认证

项目部署

1. 打包成war包

  1. 修改打包形式
    在pom.xml里设置 <packaging>war</packaging>
    在build节点中添加<finalName>ROOT</finalName>可以修改打包后的文件名称为ROOT.war
  2. 移除嵌入式tomcat插件
    下面两种方式都可以,任选其一
    【方式1】
    在pom.xml里找到spring-boot-starter-web依赖节点,在其中添加如下代码:```xml org.springframework.boot spring-boot-starter-web
     <exclusion>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-tomcat</artifactId>
     </exclusion>
    
    ```

    注意,此种方法无法直接在开发环境中运行war包。


【方式2】```xml

org.springframework.boot spring-boot-starter-tomcat provided

> 注意,2017版IDEA有bug,不会将 scope为 provided 的包加载到类路径下,导致项目无法正常启动。
> **scope的属性**
> - **compile**:参与编译,测试,运行,打包。是默认值。
> - **test**:参与测试相关工作,包括测试代码的编译和执行,不参与打包。类似于junit。
> - **runtime**:参与运行、测试。类似于jdbc,不参与项目的编译。
> - **provided**:参与编译,测试,运行,不参与打包。
> - **system**:与provided类似,依赖项会从本地拿,而不是中央仓库。


3. 修改启动类,并重写初始化方法<br />我们平常用main方法启动的方式,都有一个App的启动类,在`Application`类的同级添加一个`SpringBootStartApplication`类,其代码如下:```java
/**
 * 修改启动类,继承 SpringBootServletInitializer 并重写 configure 方法
 */
public class SpringBootStartApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 注意这里要指向原先用main方法执行的Application启动类
        return builder.sources(Application.class);
    }
}
  1. 打包部署
    在项目根目录下(即包含pom.xml的目录),在命令行里输入:mvn clean package,等待打包完成,出现[INFO] BUILD SUCCESS即为打包成功。输出文件在target目录下
    或者直接在在IDEA中点击Maven Projects -> Lifecycle -> package(双击) 即可。

2. 运行 war 包

2.1 直接运行

pom文件中不能移除嵌入式tomcat

在命令行中使用命令java -jar XXXX.war即可运行

使用java -jar XXXX.war --server.port=8888自定义端口号,需要考虑前端是否能请求到对应端口号的资源

在配置文件中添加server.port=80可以设置运行时的端口号。

2.2 在tomcat中运行

pom文件中需要移除嵌入式tomcat

将war包命名为ROOT.war,删除tomcat安装目录下的webapps中的ROOT文件夹,将ROOT.war拷贝到webapps文件夹下,在bin目录下执行startup.bat运行tomcat,即可在浏览器中访问。

3. 开发环境

3.1 多环境配置

SpringBoot的配置文件有以下4个:

  • application.properties 默认配置(程序默认加载这个)
  • application-dev.properties 开发环境
  • application-prod.properties 运行环境
  • application-test.properties 测试环境

application.properties中添加内容:

spring.profiles.active=dev

说明是项目当前处于dev环境,会继续加载application-dev.properties文件。

3.2 热部署

如果想要实现刷新静态资源的话:

如果使用了thymeleaf的话,要禁用掉页面缓存(推荐在application-dev.properties中设置):

spring.thymeleaf.cache=false

在IDEA中设置 “File -> Settings -> Build -> Compiler -> 勾选Build project automatically”,按快捷键 ctrl + shift + alt + /,选择Registry,勾上 “Compiler autoMake allow when app running”,然后点击确定。

如果想要自动重启项目的话:

添加maven依赖

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
</dependency>

在配置文件中开启热部署功能

spring.devtools.restart.enabled: true