谈谈SpringBoot(二)

1. Spring Boot缓存

1.1 JSR-107

Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager接口来统一不同的缓存技术;
并支持使用JCache(JSR-107)注解简化我们开发。

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache, ConcurrentMapCache等;
  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取;
  • 使用Spring缓存抽象时我们需要关注以下两点:
    1、确定方法需要被缓存以及他们的缓存策略
    2、从缓存中读取之前缓存存储的数据

 1.2 Springboot缓存抽象的缓存注解

Cache缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager缓存管理器,管理各种缓存(Cache)组件
@Cacheable主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict清空缓存
@CachePut保证方法被调用,又希望结果被缓存
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略

    @Cacheable/@CachePut/@CacheEvict 主要的参数

value缓存的名称,在spring 配置文件中定义,必须指定至少一个例如:@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}
key缓存的key,可以为空,如果指定要按照SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用SpEL 编写,返回true 或者false,只有为true 才进行缓存/清除缓存例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries
(@CacheEvict )
是否清空所有缓存内容,缺省为false,如果指定为true,则方法调用后将立即清空所有缓存例如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation
(@CacheEvict)
是否在方法执行前就清空,缺省为false,如果指定为true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存例如:
@CachEvict(value=”testcache”,beforeInvocation=true)

1.3 Springboot缓存注解的使用

1.3.1 创建工程

参照:谈谈SpringBoot(一)9. SpringBoot与数据访问

/resources/application.properties

#开启驼峰命名
#mybatis.configuration.map-underscore-to-camel-case=true
#将日志级别设置为debug
logging.level.com.zmj.springboot.mapper=debug
#打开自动配置报告
debug=true

pom.xml依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.12.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.zmj</groupId>
    <artifactId>springboot-cache</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-cache</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.15</version>
        </dependency>
        <dependency>
            <groupId>org.apache.log4j</groupId>
            <artifactId>com.springsource.org.apache.log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

​​不使用时,每次查询都要进入数据库

​​

1.3.2  使用缓存

@Service
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;

    @Cacheable(cacheNames = {"emp"})//缓存组件名称为emp
    public Employee getEmpById(Integer id){
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
}
@RestController
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee queryEmp(@PathVariable("id") Integer id){
        return employeeService.getEmpById(id);
    }
}
@EnableCaching //开启缓存
@MapperScan("com.zmj.springboot.mapper")
@SpringBootApplication
public class SpringbootCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootCacheApplication.class, args);
    }
}

使用缓存后,第一次查询进入数据库,后面如果是相同的数据,直接从缓存中获取

1.4 @Cacheable的工作原理和流程

1.4.1) 服务启动,执行SpringApplication.run方法

1.4.2) 首先找到缓存的自动配置类,根据命名规范可知是CacheAutoConfiguration

@Configuration
@ConditionalOnClass({CacheManager.class})
@ConditionalOnBean({CacheAspectSupport.class})
@ConditionalOnMissingBean(
    value = {CacheManager.class},
    name = {"cacheResolver"}
)
@EnableConfigurationProperties({CacheProperties.class})
@AutoConfigureBefore({HibernateJpaAutoConfiguration.class})
@AutoConfigureAfter({CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, RedisAutoConfiguration.class})
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})
public class CacheAutoConfiguration {
}

1.4.3) 通过@Import得知,需要导入CacheConfigurationImportSelector类

@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})
public class CacheAutoConfiguration {
    static class CacheConfigurationImportSelector implements ImportSelector {
        CacheConfigurationImportSelector() {
        }
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            CacheType[] types = CacheType.values();
            String[] imports = new String[types.length];

            for(int i = 0; i < types.length; ++i) {
                imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
            }

            return imports;
        }
    }
}

​1.4.4) 缓存的自动配置类中,默认SimpleCacheConfiguration类生效

----通过打印出来的自动配置报告获得   

SimpleCacheConfiguration matched:
      - Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type (CacheCondition)
      - @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)

@Configuration
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {

    @Bean
    public ConcurrentMapCacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }
        return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
    }
}

1.4.5) SimpleCacheConfiguration的ConcurrentMapCacheManager

ConcurrentMapCacheManager中有一个cacheMap,后来的缓存数据就是保存到这里

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
}

1.4.6)浏览器发送第一次请求

http://localhost:8080/emp/1

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
    public Cache getCache(String name) {
        Cache cache = (Cache)this.cacheMap.get(name);//name=emp
        if (cache == null && this.dynamic) {//第一次请求缓存为null且this.dynamic为true
            synchronized(this.cacheMap) {
                cache = (Cache)this.cacheMap.get(name);
                if (cache == null) {//再次检测,还是为null
                    cache = this.createConcurrentMapCache(name);//创建新缓存
                    this.cacheMap.put(name, cache);
                }
            }
        }
        return cache;
    }
}

 1.4.7)创建新缓存对象

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    protected Cache createConcurrentMapCache(String name) {
        SerializationDelegate actualSerialization = this.isStoreByValue() ? this.serialization : null;
        return new ConcurrentMapCache(name, new ConcurrentHashMap(256), this.isAllowNullValues(), actualSerialization);
    }
}

 1.4.8)生成key和value(cache对象)

key是默认按照keyGenerator策略生成的,默认是使用SimpleKeyGenerator生成key

value是通过回调函数,调用EmployeeService.getEmpById()拿到实例对象

public abstract class CacheAspectSupport extends AbstractCacheInvoker implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
    private Object execute(final CacheOperationInvoker invoker, Method method, CacheAspectSupport.CacheOperationContexts contexts) {
        if (contexts.isSynchronized()) {
            CacheAspectSupport.CacheOperationContext context = (CacheAspectSupport.CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
            if (this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
                Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
                Cache cache = (Cache)context.getCaches().iterator().next();

                try {
                    return this.wrapCacheValue(method, cache.get(key, new Callable<Object>() {
                        public Object call() throws Exception {
                            return CacheAspectSupport.this.unwrapReturnValue(CacheAspectSupport.this.invokeOperation(invoker));
                        }
                    }));
                } catch (ValueRetrievalException var10) {
                    throw (ThrowableWrapper)var10.getCause();
                }
            } else {
                return this.invokeOperation(invoker);
            }
        } else {
            this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
            ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
            List<CacheAspectSupport.CachePutRequest> cachePutRequests = new LinkedList();
            if (cacheHit == null) {
                this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
            }

            Object cacheValue;
            Object returnValue;
            if (cacheHit != null && cachePutRequests.isEmpty() && !this.hasCachePut(contexts)) {
                cacheValue = cacheHit.get();
                returnValue = this.wrapCacheValue(method, cacheValue);
            } else {
                returnValue = this.invokeOperation(invoker);
                cacheValue = this.unwrapReturnValue(returnValue);
            }

            this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
            Iterator var8 = cachePutRequests.iterator();

            while(var8.hasNext()) {
                CacheAspectSupport.CachePutRequest cachePutRequest = (CacheAspectSupport.CachePutRequest)var8.next();
                cachePutRequest.apply(cacheValue);
            }

            this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
            return returnValue;
        }
    }
    protected class CacheOperationContext implements CacheOperationInvocationContext<CacheOperation> {
        public CacheOperationContext(CacheAspectSupport.CacheOperationMetadata metadata, Object[] args, Object target) {
        protected Object generateKey(Object result) {
            if (StringUtils.hasText(this.metadata.operation.getKey())) {
                EvaluationContext evaluationContext = this.createEvaluationContext(result);
                return CacheAspectSupport.this.evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
            } else {
                return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
            }
        }
    }
}

 1.4.9)浏览器发送第二次请求

http://localhost:8080/emp/1

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    public Cache getCache(String name) {
        Cache cache = (Cache)this.cacheMap.get(name);//name=emp
        if (cache == null && this.dynamic) {//此时cache不为空,条件不成立
            synchronized(this.cacheMap) {
                cache = (Cache)this.cacheMap.get(name);
                if (cache == null) {
                    cache = this.createConcurrentMapCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }
        return cache;//直接返回缓存数据
    }
}

1.5 @Cacheable的其他属性

1.5.1 第一种方式

@Service
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;

    //@Cacheable(cacheNames = {"emp"})//缓存组件名称为emp
    //@Cacheable(cacheNames = {"emp"},condition = "#id > 0")//缓存的条件是id > 0
    @Cacheable(cacheNames = {"emp"},key="#root.methodName+'['+#id+']'")//key=方法名+id
    public Employee getEmpById(Integer id){
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
}

1.5.2 第二种方式

@Configuration
public class MyCacheConfig {

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator() {//自定义key的生成策略
        return new KeyGenerator(){
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+ "["+Arrays.asList(params).toString()+"]";
            }
        };
    }
}
@Service
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;

    @Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator")//key为自定义策略
    //@Cacheable(cacheNames = {"emp"},keyGenerator="myKeyGenerator",condition = "#id > 1")
    //@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator",condition = "#id > 1",unless = "#a0==2")
    public Employee getEmpById(Integer id){
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
}

1.6 其他缓存注解

@CacheConfig(cacheNames = {"emp"})//全局配置缓存名字都是emp
@Service
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;

    @Cacheable()
    //@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator",condition = "#id > 1",unless = "#a0==2")
    public Employee getEmpById(Integer id){
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
    //更新缓存,key为员工ID
    @CachePut(key = "#employee.id")
    public Employee updateEmp(Employee employee) {
        employeeMapper.updateEmp(employee);
        return employee;
    }

    //@CacheEvict(allEntries = true)//清除所有缓存
    @CacheEvict(key = "#id")//清除缓存,key为员工ID
    @CacheEvict(key = "#id")
    public void deleteEmp(Integer id) {
        employeeMapper.deleteEmp(id);
    }

    //@Caching定义复杂的缓存规则
    @Caching(
            cacheable = {
                    @Cacheable(key = "#lastName")
            },
            put = {
                   @CachePut(key = "#result.id"),
                   @CachePut(key = "#result.email")
            }
    )
    public Employee getEmpByLastName(String lastName){
        Employee emp = employeeMapper.getEmpByLastName(lastName);
        return emp;
    }
}

1.7 Springboot整合Redis

1.7.1 Docker中启动Redis

参照:谈谈Docker12. Docker安装Redis谈谈Docker

docker run -d -p 16379:6379 --name myredis8 16ecd2772934

1.7.2  依赖和配置

在缓存的基础上添加pom.xml依赖

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

在application.properties里面追加 

spring.redis.host=192.168.1.200
spring.redis.port=16379
@Configuration
public class MyRedisConfig {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Object> ser = new Jackson2JsonRedisSerializer<>(Object.class);
        template.setDefaultSerializer(ser);//序列化器
        return template;
    }
}

1.7.3 在测试类中验证redis

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootCacheApplicationTests {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private EmployeeMapper employeeMapper;

    @Test
    public void stringRedisTemplateTest() {
        stringRedisTemplate.opsForValue().set("str","1");//设置key=str,value=1
        stringRedisTemplate.opsForValue().append("str","2");//key=str的value追加2
        String str = stringRedisTemplate.opsForValue().get("str");//获取key=str的value
        System.out.println(str);//结果为12

        stringRedisTemplate.opsForList().leftPush("myList","a");//为集合myList左边放一个a
        stringRedisTemplate.opsForList().leftPush("myList","b");//为集合myList左边放一个b
        String pop1 = stringRedisTemplate.opsForList().leftPop("myList");
        System.out.println("pop1="+pop1);//pop1=b
        String pop2 = stringRedisTemplate.opsForList().leftPop("myList");
        System.out.println("pop2="+pop2);//pop2=a
    }

    @Test
    public void redisTemplateTest() {
        Integer id = 1;
        Employee emp = employeeMapper.getEmpById(id);//emp.lastName=张三
        redisTemplate.opsForValue().set(String.valueOf(id),emp);
        LinkedHashMap linkedHashMap = (LinkedHashMap) redisTemplate.opsForValue().get(String.valueOf(id));
        System.out.println(linkedHashMap.get("lastName"));//张三
    }
}

1.7.4 页面访问

@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;
 
    @GetMapping("/dept/{id}")
    public Department queryDepartment(@PathVariable("id") Integer id){
        return deptService.getDeptById(id);
    }
}
@CacheConfig(cacheNames = {"dept"})//全局配置缓存名字都是dept
@Service
public class DeptService {
    @Autowired
    private DepartmentMapper departmentMapper;

    @Cacheable()
    public Department getDeptById(Integer id){
        Department dept = departmentMapper.getDeptById(id);
        return dept;
    }
}
@Configuration
public class MyRedisConfig {
   // 配置Jackson2JsonRedisSerializer
    // 避免出现获取缓存时出现的类型转换错误
    private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

        // 此项必须配置
        //否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }

    @Bean
    @ConditionalOnMissingBean(
            name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Object> jackson2RedisSerializer = jackson2JsonRedisSerializer();
        //默认使用jdk序列化
        template.setDefaultSerializer(jackson2RedisSerializer);
        return template;
    }
}

除了第一次访问,后面几次都不会请求数据库,直接查询Redis缓存数据 

2. Spring Boot与消息

2.1 概述

1. 在大多应用中,我们系统之间需要进行异步通信,即异步消息


2. 异步消息中两个重要概念:
        消息代理(message broker)和目的地(destination)
当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地


3. 异步消息主要有两种形式的目的地
a.队列(queue):点对点消息通信(point-to-point)
b.主题(topic):发布(publish)/订阅(subscribe)消息通信

4.点对点式:
–消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列
–消息只有唯一的发送者和接受者,但并不是说只能有一个接收者


5.发布订阅式:
–发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时同时收到消息


6.JMS(Java Message Service)java消息服务:
–基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现


7.AMQP(Advanced Message Queuing Protocol)
–高级消息队列协议,也是一个消息代理的规范,兼容JMS
–RabbitMQ是AMQP的实现

8.Spring支持
–spring-jms提供了对JMS的支持
–spring-rabbit提供了对AMQP的支持
–需要ConnectionFactory的实现来连接消息代理
–提供JmsTemplate、RabbitTemplate来发送消息
–@JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
–@EnableJms、@EnableRabbit开启支持


9.Spring Boot自动配置
–JmsAutoConfiguration
–RabbitAutoConfiguration

2.2 开启RabbitMQ服务

参照:21.Docker安装RabbitMQ

2.3 搭建工程

application.properties配置信息

spring.rabbitmq.host=192.168.1.200
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin

2.4 测试单播模式

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAmqpApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    public void unicastMode() {//单播模式(点对点)
        Map<String,Object> map = new HashMap<>();
        map.put("msg","idea:helloworld");
        map.put("data", Arrays.asList("nihao",123,true));
        String exchange = "exchage.direct";//交换器
        String routingKey = "zmj.news";//路由键
        rabbitTemplate.convertAndSend(exchange,routingKey,map);
    }
}

 1)运行测试类后,结果如下。说明数据是被序列化后发送的

@Configuration
public class MyAMQPConfig {

    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();//数据转为JSON格式
    }
}

2)再次运行测试类后,结果已经变为了JSON格式

 3)接收数据

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAmqpApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void receiveMsg() {//接收消息
        Object obj = rabbitTemplate.receiveAndConvert("zmj.news");
        System.out.println(obj);
        //{msg=idea:helloworld, data=[nihao, 123, true]}
    }
}

2.5 测试广播模式

    @Test
    public void broadcastMode() {//广播模式
        String exchange = "exchage.fanout";//交换器
        String routingKey = "";//路由键,广播模式不用指定
        rabbitTemplate.convertAndSend(exchange,routingKey,new Book("西游记","吴承恩"));
    }

2.6 监听

@EnableRabbit //开启基于注解的RabbitMQ
@SpringBootApplication
public class SpringbootAmqpApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootAmqpApplication.class, args);
    }
}
@Service
public class BookService {
    //只要队列zmj.news里面收到消息,就会打印
    @RabbitListener(queues = "zmj.news")
    public void receive(Book book) {
        System.out.println("收到消息,Book:"+book);
    }
}

比如 服务启动后,先运行下测试类的广播模式,这样zmj.news肯定会收到消息

    @Test
    public void broadcastMode() {//广播模式
        String exchange = "exchage.fanout";//交换器
        String routingKey = "";//路由键,广播模式不用指定
        rabbitTemplate.convertAndSend(exchange,routingKey,new Book("三国演义","罗贯中"));
    }

控制台在zmj.news队列收到消息后,打印结果

收到消息,Book:Book{name='三国演义', author='罗贯中'}

  @RabbitListener(queues = "zmj")
  public void receiveMsg(Message message) {
      System.out.println(message.getBody());//[B@64d6df90
      System.out.println(message.getMessageProperties());
      //MessageProperties [headers={__TypeId__=com.zmj.springboot.entity.Book}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=true, receivedExchange=exchage.fanout, receivedRoutingKey=, deliveryTag=1, consumerTag=amq.ctag-h1nXe1GQmX1C4-KCRzAAug, consumerQueue=zmj]
  }

2.7 AmqpAdmin管理组件

    @Test
    public void create() {
        String exchangeName = "amqpadmin.directexchange";//交换器名称
        DirectExchange directExchange = new DirectExchange(exchangeName);//direct交换器
        amqpAdmin.declareExchange(directExchange);//创建direct交换器

        String queueName = "amqpadmin.queue";//队列名称
        Queue durableQueue = new Queue(queueName, true);//持久化类型的队列
        amqpAdmin.declareQueue(durableQueue);//创建队列

        String routingKeyName = "amqpadmin.routingkey";//路由键名称
        Binding binding = new Binding(queueName, Binding.DestinationType.QUEUE, exchangeName, routingKeyName, null);
        amqpAdmin.declareBinding(binding);//创建绑定规则
    }

3. Spring Boot与检索 

3.1 Elasticsearch简介

        我们的应用经常需要添加检索功能,开源的 ElasticSearch 目前全文搜索引擎的首选可以快速的存储、搜索和分析海量数据。Spring Boot通过整合Spring Data ElasticSearch为我们提供了非常便捷的检索功能支持;

        Elasticsearch是一个分布式搜索服务,提供Restful API,底层基于Lucene,采用多shard(分片)的方式保证数据安全,并且提供自动resharding的功能,github等大型的站点也是采用了ElasticSearch作为其搜索服务

         员工文档 的形式存储为例:一个文档代表一个员工数据。存储数据到 ElasticSearch 的行为叫做 索引 ,但在索引一个文档之前,需要确定将文档存储在哪里。

        一个 ElasticSearch 集群可以 包含多个 索引 ,相应的每个索引可以包含多个 类型 。 这些不同的类型存储着多个 文档 ,每个文档又有 多个 属性 。

        类似关系(对比MySQL):

索引 <===>  数据库
类型 <===> 表
文档 <===> 表中的记录
属性 <===> 列

参考: Elasticsearch: 权威指南 | Elastic

索引员工文档 | Elasticsearch: 权威指南 | Elastic

3.2 Docker 启动Elasticsearch

参考:22. Docker安装Elasticsearch

3.3 搭建工程

 

4. Spring Boot与任务 

4.1 新建工程

 

4.2 同步调用 

如果同步访问hello,那么需要等待5秒钟才会返回结果

@Service
public class AsyncService {
    public void hello() {
        try {
            Thread.sleep(5000);//模拟处理业务,耗时5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("处理数据中。。。。");
    }
}
@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

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

4.3 异步调用 

如果异步访问hello,那么不需要等待那5秒钟,直接返回结果

public class AsyncService {
    
    @Async //异步调用
    public void hello() {
        try {
            Thread.sleep(5000);//模拟处理业务,耗时5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("处理数据中。。。。");
    }
}
@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {

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

4.4 定时任务

4.4.1 简介

       项目开发中经常需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息。Spring为我们提供了异步执行任务调度的方式,提供TaskExecutor、TaskScheduler接口。

       两个注解:@EnableScheduling、@Scheduled

4.4.2 cron表达式

字段            允许值                                            允许的特殊字符 
秒               0-59                                                    , - * / 
分               0-59                                          , - * / 
小时            0-23                               , - * / 
日期            1-31                                                    , - * ? / L W C 
月份            1-12 或者 JAN-DEC              , - * / 
星期            0-7或SUN-SAT,0和7都是SUN          , - * ? / L C # 
年(可选) 留空, 1970-2099                   , - * / 

*   表示所有值
?  表示不关心它为何值。一般用于日和星期冲突。确定日是几,则星期为?。确定是星期几,则日为?
-   表示区间
,   表示枚举 
/   符号前表示开始时间,符号后表示每次递增的值
L   最后 
W 工作日
C 指和calendar联系后计算过的值

@Service
public class ScheduledService {
    /**
     * second minute hour dayOfMonth month dayOfWeek
     * 秒       分    时      日       月     周几
     *  0 0/5 14,18 * * ?    每天14点整和18点整,每隔5分钟执行一次
     *  0 15 10 ? * 1-6      每个月的周一至周六10:15分执行一次
     *  0 0 2 ? * 6L         每个月的最后一个周六凌晨2点执行一次
     *  0 0 2 LW * ?         每个月的最后一个工作日凌晨2点执行一次
     *  0 0 2-4 ? * 1#1      每个月的第一个周一凌晨2点到4点期间,每个整点都执行一次
     *  0/4 * * * * MON-SAT  周一到周六每隔4秒执行一次
     */
    @Scheduled(cron = "0 * * * * MON-FRI")
    public void hello(){
        System.out.println("周一到周五每分钟就执行一次......");
    }
}

4.5 邮件服务

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

application.properties

spring.mail.username=1074005204@qq.com
spring.mail.password=odaptjcssvpnhgch
spring.mail.host=smtp.qq.com
@SpringBootTest
public class SpringbootApplicationTests {

    @Autowired
    private JavaMailSenderImpl mailSender;

    @Test
    public void sendSimpleMail() {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setSubject("通知-今晚开会");
        message.setText("今晚19:30开会");
        message.setTo("dream98job@126.com");
        message.setFrom("1074005204@qq.com");
        mailSender.send(message);
    }

    @Test
    public void sendMimeMail() throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setSubject("通知-今晚开会");

        helper.setText("<b style='color:red'>今晚19:30开会</b>",true);
        helper.setTo("dream98job@126.com");
        helper.setFrom("1074005204@qq.com");
        helper.addAttachment("a.jpg",new File("E:\\MySelfPic\\P80204-150655.jpg"));
        mailSender.send(mimeMessage);
    }
}

5. Spring Boot与安全

5.1 Spring Security简介

        Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他可以实现强大的web安全控制。对于安全控制,我们仅需引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理。

        应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。

        “认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。你是谁?

        “授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。你有什么权限?

5.2 搭建工程

6. Spring Boot与Spring Cloud

6.1 Spring Cloud简介

Spring Cloud是一个分布式的整体解决方案。Spring Cloud 为开发者提供了在分布式系统(配置管理,服务发现,熔断,路由,微代理,控制总线,一次性token,全局琐,leader选举,分布式session,集群状态)中快速构建的工具,使用Spring Cloud的开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。

SpringCloud分布式开发五大常用组件

        服务发现——Netflix Eureka

        客服端负载均衡——Netflix Ribbon

        断路器——Netflix Hystrix

        服务网关——Netflix Zuul

        分布式配置——Spring Cloud Config

6.2 搭建工程

6.2.1 注册中心-Eureka

 

 application.yml

server:
  port: 8761
eureka:
  instance:
    hostname: eureka-server #eureka实例的主机名
  client:
    register-with-eureka: false #不把自己注册到eureka上
    fetch-registry: false #不从eureka上来获取服务的注册信息
    service-url:
      defaultZone: http://localhost:8761/eureka/
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

登录 http://localhost:8761 

目前显示还没有服务注册进来 

6.2.2 服务提供者

@Service
public class TicketService {
    public String getTicket() {
        return "《敲代码的人真帅》";
    }
}
@RestController
public class TicketController {

    @Autowired
    private TicketService ticketService;

    @GetMapping("/ticket")
    public String getTicket(){
        return ticketService.getTicket();
    }
}

application.yml

server:
  port: 8001
spring:
  application:
    name: provider-ticket
eureka:
  instance:
    prefer-ip-address: true #注册服务时 使用服务的IP
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

  分别设置不同端口,打成两个jar包 

java -jar  provider-ticket-0.0.1-SNAPSHOT-8001.jar
java -jar  provider-ticket-0.0.1-SNAPSHOT-8002.jar

此时服务被注册进来

 6.2.3 服务消费者

 application.yml

server:
  port: 8200
spring:
  application:
    name: consumer-user
eureka:
  instance:
    prefer-ip-address: true #注册服务时 使用服务的IP
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
@EnableDiscoveryClient //开启发现服务功能
@SpringBootApplication
public class ConsumerUserApplication {

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

    @LoadBalanced //使用负载均衡机制
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
@RestController
public class UserController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/buy")
    public String buyTicket(String name) {
        String str = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class);
        return "购买了"+str;
    }
}

 启动后

 

 7. Spring Boot与开发热部署

7.1 按照6的内容继续将/buy改为/buy2

@RestController
public class UserController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/buy2")
    public String buyTicket(String name) {
        String str = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class);
        return "购买了"+str;
    }
}

 

 7.2 引入依赖并重启 

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

 7.3 将/buy2改为/buyTicket

@RestController
public class UserController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/buyTicket")
    public String buyTicket(String name) {
        String str = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class);
        return name + "购买了"+str;
    }
}

Ctrl + F9让代码生效 

8.Spring Boot与监控管理

8.1 简介

        通过引入spring-boot-starter-actuator,可以使用Spring Boot为我们提供的准生产环境下的应用监控和管理功能。我们可以通过HTTPJMXSSH协议来进行操作,自动得到审计、健康及指标信息等

        步骤:

                引入spring-boot-starter-actuator

                通过http方式访问监控端点

                可进行shutdownPOST 提交,此端点默认关闭)

8.2 搭建工程

localhost:8080/actuator/

 相关内容谈谈SpringBoot(一)

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

朱梦君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值