zoukankan      html  css  js  c++  java
  • Spring Boot Actuator 的使用

      Actuator其实是监控Spring Boot应用的一些信息,如依赖关系,路径处理器关系,线程信息,JVM信息等,但是这些信息都是以json格式返回,没有可视化的页面,需要自己去搜索想要了解的详细信息。

      而这些信息可以通过三种方式获取:

        1.Actuator端点方式,Rest请求获取(使用spring-boot-starter-actuator自启动,如果不是web工程,还需要加spring-webmvc的依赖)

        2.远程shell,使用命令查看(使用spring-boot-starter-remote-shell自启动,监听端口为2000,用户名默认为user,密码会在启动日志可以查看到)

        3.JMX方式

      Actuator端点信息:

      

      在使用Actuator端点查看信息时,有如下敏感信息会被隐藏,查看不了(页面出现错误信息的情况

      

      解决的方法是在application.properties加入如下配置:

        management.security.enabled=false

      

      如果我们需要对Actuator进行个性化设置,可在以下几方面进行定制:

        1.端点重命名(避免请求路径冲突或者避免默认请求暴露,例如在application.properties中加上配置:endpoint.beans.id=beanlist)

        2.端点禁用与启用(例如在application.properties中加上配置:endpoint.beans.enabled=false)

        3.自定义度量信息,针对于metrics端点信息

          3.1如果是统计的话,可使用CounterService接口跟GaugeService接口,无需实现,spring已经帮我们实现了,只要引用就好

    @Controller
    @RequestMapping("/myworld")
    public class MyController {
    
        @Resource
        private CounterService counterService;
    
        @Resource
        private GaugeService gaugeService;
    
        @ResponseBody
        @RequestMapping("hello")
        public String hello(){
            // 在metrics端点增加hello request属性(访问计数)
            counterService.increment("hello.request");
            // 属性时间更新
            gaugeService.submit(
                    "hello.request.saved", System.currentTimeMillis());
            return  "hello";
        }
    }

          3.2如果需要自定义的一些信息,可实现PublicMetrics接口

    @Component
    public class ApplicationContextMetrics implements PublicMetrics {
    
        @Resource
        private ApplicationContext context;
    
        @Override
        public Collection<Metric<?>> metrics() {
            List<Metric<?>> metrics = new ArrayList<Metric<?>>();
            // 启动时间
            metrics.add(new Metric<Long>("spring.context.startup-date",
                    context.getStartupDate()));
            // bean的总数量
            metrics.add(new Metric<Integer>("spring.beans",
                    context.getBeanNamesForType(Object.class).length));
            // Controller的数量
            metrics.add(new Metric<Integer>("spring.controllers",
                    context.getBeanNamesForAnnotation(Controller.class).length));
            return metrics;
        }
    }

        4.自定义跟踪数据的仓库(主要针对trace端点,默认保留100条跟踪信息,新来的会覆盖旧有的,我们可以加大容量设为1000或者10000,但还有一种方法就是自定义仓库,实现TraceRepository接口)

         我使用的是redis作为跟踪数据的仓库,数据结构采用的set集合,也可以使用list列表,java实体转换json使用的是fastjson

         引入redis自启动(如果引入不进来,可指定版本号,有可能spring-boot-starter-redis的版本号不及spring-boot版本的新,如redis自启动版本是1,

                 而spring-boot的版本是2,默认去找版本号为2的redis自启动是找不到的

         <dependency>  

          <groupId>org.springframework.boot</groupId>  

          <artifactId>spring-boot-starter-redis</artifactId>  

         </dependency

        具体spring-boot的redis使用可参考网上例子,这里不做详细描述,以后可写一篇关于spring redis使用的博文。

        在application.properties加入redis的配置:

    # REDIS (RedisProperties)
    # Redis数据库索引(默认为0)
    spring.redis.database=0
    # Redis服务器地址
    spring.redis.host=127.0.0.1
    # Redis服务器连接端口
    spring.redis.port=6379
    # Redis服务器连接密码(默认为空)
    spring.redis.password=
    # 连接池中的最大空闲连接
    spring.redis.pool.max-idle=200
    # 连接池中的最小空闲连接
    spring.redis.pool.min-idle=100
    spring.redis.pool.max-wait=-1
    # 连接超时时间(毫秒)
    spring.redis.timeout=3000

        代码如下:

    @Component
    public class RedisTraceRepository implements TraceRepository {
    
        @Autowired
        private JedisService jedisService;
    
        @Override
        public List<Trace> findAll() {
            return jedisService.findObjectSet(Trace.class);
        }
    
        @Override
        public void add(Map<String, Object> map) {
            jedisService.objectSet((new Trace(new Date(), map)));
        }
    }
    @Service
    public class JedisService {
    
        private static final Map<String, String> mapNames = new ConcurrentHashMap();
    
        @Autowired
        private RedisTemplate redisTemplate;
    
           /**
         * 集合添加
         * @param key
         * @param value
         */
        public void add(String key,Object value){
            SetOperations<String, Object> set = redisTemplate.opsForSet();
            set.add(key,value);
        }
    
        /**
         * 集合获取
         * @param key
         * @return
         */
        public Set<Object> setMembers(String key){
            SetOperations<String, Object> set = redisTemplate.opsForSet();
            return set.members(key);
        }
    
        private static final boolean isUpperEnglishChar(char value) {
            return value >= 65 && value <= 90;
        }
    
        private static final <T> String getSimpleName(String simpleName) {
            char[] chars = simpleName.toCharArray();
            StringBuffer sb = new StringBuffer();
    
            for(int i = 0; i < chars.length; ++i) {
                char c = chars[i];
                if(i > 0 && isUpperEnglishChar(c)) {
                    sb.append("_").append(String.valueOf(c).toLowerCase());
                } else {
                    sb.append(String.valueOf(c).toLowerCase());
                }
            }
    
            return new String(sb);
        }
    
        public static <T> String getMapName(Class<T> clazz) {
            String name = clazz.getName();
            String mapName = (String)mapNames.get(name);
            if(StringUtils.isEmpty(mapName)) {
                mapName = getSimpleName(clazz.getSimpleName());
                mapNames.put(name, mapName);
            }
    
            return mapName;
        }
    
    
        public <T> void objectSet(final T obj) {
            add(getMapName(obj.getClass()), JSON.toJSONString(obj));
        }
    
        public <T> List<T> findObjectSet(Class<T> clazz) {
            String key =getMapName(clazz);
            Set<Object> set = setMembers(key);
            List<T> list = new ArrayList<T>();
            String value;
            for(Object t :set){
                value =t.toString();
                if(value != null) {
                    list.add(JSON.parseObject(value,clazz));
                }
            }
            return  list;
        }
    }

         5.自定义健康指示器信息(主要针对于health端点,可实现HealthIndicator接口)

    @Component
    public class BodyHealth implements HealthIndicator {
    
        @Override
        public Health health() {
            try {
                return Health.up().withDetail("reason", "吃饱了很健康").build();
            } catch (Exception e) {
                return Health.down().withDetail("reason", "吃到一半拉肚子了").build();
            }
        }
    }

       actuator访问控制:

      像rest直接请求端点信息,未免太不安全,所有人都可以看你工程的信息了,特别是/shutdown这请求,直接关了你的工程,那很嗨了。

       所以接下来,这里结合spring security做端点访问的控制。

         首先引入依赖:

      <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-security</artifactId>

      </dependency>

      在application.properties加入配置:

    management.context-path=/admin
    management.security.enabled=true
    
    # 测试用户
    security.user.name=admin
    security.user.password=admin
    security.basic.enabled=true
    
    # 测试用户角色
    management.security.roles=ADMIN

      在写一个自定义的WebSecurityConfigurerAdapter,用于实现自己的定制实现:

    package com.zgz.security;
    
    import com.alibaba.druid.util.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    
    import java.util.Collection;
    
    /**
     * Created by zgz on 2017/12/16.
     */
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        Environment env;
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/").access("hasRole('READER')")
                        // 这里对/admin/**的请求都判断有没有ADMIN角色,还记得配置文件配了对actuator端点的访问基路径吗(/admin),
                        // 没错,为了不影响其他请求,这里只对actuator端点进行鉴权
                    .antMatchers("/admin/**").access("hasRole('ADMIN')")
                    .antMatchers("/**").permitAll().and().formLogin().and().httpBasic();;
        }
    
        /** 如果不想在配置文件添加测试用户,也可在这里添加用户
        @Override
        protected void configure(
                AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("admin").password("admin")
                    .roles("ADMIN");
        }
        **/
    }

      以上这里只是为了举例实现对actuator的访问控制,其实spring security还有很多可说,这里就不做详述,以后可再写一篇关于spring security的博文。

      以上纯属个人整理跟理解,如有错误,请帮忙纠正我,在此感谢!

  • 相关阅读:
    bk.
    仅仅为了记录
    一个简单的Lua解释器
    Lua与C++相互调用
    Struts标签、Ognl表达式、el表达式、jstl标签库这四者之间的关系和各自作用
    OGNL表达式struts2标签“%,#,$”
    Java异常报错机制
    到底EJB是什么?
    Spring总结
    JSON(JavaScript Object Notation)
  • 原文地址:https://www.cnblogs.com/zgz21/p/8038107.html
Copyright © 2011-2022 走看看