1. Java自定义注解
1.1注解定义
注解通过@interface关键字进行定义
public @interface TestAnnotation{
}
创建一个名称为TestAnnotation的注解,形式和接口类似,多了个@符号
1.2 注解的应用
@TestAAnnotation
public class Test{
}
创建一个类Test,然后在类定义的地方加上@TestAnnotation
注解的使用还需要搭配上元注解
1.3 元注解
元注解:是一种基本注解,可以应用到其他注解上面
元注解标签有:@Retention,@Documented,@Target,@Inherited,@Repeatable
1.3.1 @Retention
Retention的英文时候保留期,注解的作用是说明这个注解的存活时间
属性如下
注解名称 | 注解作用 |
---|---|
RetentionPolicy.SOURCE | 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视 |
RetentionPolicy.CLASS | 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中 |
RetentionPolicy.RUNTIME | 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们 |
使用方法
@Retention(RententionPolicy.RUNTIME)
public @inteface TestAnnotation{
}
指定了注解TestAnnotation注解的存活时间为RUNTIME,可以在程序运行周期被获取到
1.3.2 @Documented
它的作用是将注解中的元素包含到Javadoc中
没有什么具体的属性
1.3.3 @Target
@Target指定了注解运用的地方,当一个注解被@Target注解时,这个注解就被限定了运用的场景
属性如下
注解名称 | 注解作用 |
---|---|
ElementType.ANNOTATION_TYPE | 可以给一个注解进行注解 |
ElementType.CONSTRUCTOR | 可以给构造方法进行注解 |
ElementType.FIELD | 可以给属性进行注解 |
ElementType.LOCAL_VARIABLE | 可以给局部变量进行注解 |
ElementType.METHOD | 可以给方法进行注解 |
ElementType.PACKAGE | 可以给一个包进行注解 |
ElementType.PARAMETER | 可以给一个方法内的参数进行注解 |
ElementType.TYPE | 可以给一个类型进行注解,比如类、接口、枚举 |
1.3.4 @Inherited
Inherited是继承的意思,但是并不是说注解本身可以继承而是说如果一个超类被@Inherited注解过的注解进行注解的话,那么如果他的子类没有被任何注解应用的话,那么这个子类继承了超类的注解
使用方法
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test{}
@Test
public class{}
public class B extends A{}
注解Test被@Inherited修饰,之后类A被Test注解,类B继承A,类B也拥有Test注解
1.3.5 @Repeatable
Repeatable是可重复的意思,表示这个注解可以多次运用,通常是注解的值可以同时取多个
使用方法
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
@Repeatable注解了Person,而@Repeateable后面的括号中的类相当于一个容器注解
容器注解:用来存放其他注解的地方,本身也是注解
代码中的容器注解
@interface Persons {
Person[] value();
}
按照规定,定义中必须有一个value的属性,属性类型是被@Repeatable注解过的注解数组
如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。
代码中的@Person(role=“artist”)还涉及到了注解的属性
1.4 注解的属性
注解的属性也叫做成员变量,注解只有成员变量,没有方法,注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
代码中i定义了TestAnnotation这个注解中拥有id和msg两个属性,使用时需要给他们赋值
赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
TestAnnotation中id属性默认值为-1,msg属性默认值为Hi
@TestAnnotation()
public class Test {}
因为有默认值,所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了,这一步可以省略。
另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。
public @interface Check {
String value();
}
@Check("hi")
int a;
//两个效果相同
@Check(value="hi")
int a;
还需要注意一种情况是一个注解没有任何属性
public @interface Perform {}
应用时连括号都可以省略
@Perform
public void testMethod(){}
1.5 java预置的注解
1.5.1 @Deprecated
这个元素是用来标记过时的元素,编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
public class Hero {
@Deprecated
public void say(){
System.out.println("Noting has to say!");
}
public void speak(){
System.out.println("I have a dream!");
}
}
调用say() 方法的话会被一条直线划了,这其实就是编译器识别后的提醒效果。
1.5.2 @Override
提示子类要复写父类中被 @Override 修饰的方法
1.5.3 @SuppressWarnings
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
@SuppressWarnings("deprecation")
public void test1(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
1.5.4 @SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
@SafeVarargs // Not actually safe!
static void m(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // Semantically invalid, but compiles without warnings
String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
1.5.5 @FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
2. Spring中的ApplicationListener
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener{}
ApplicationListener<>里边的泛型需要继承ApplicationEvent的抽象方法
项目中的使用
public class HandleInit implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Map<String,Object> beans = event.getApplicationContext().getBeansWithAnnotation(MsgType.class);
for(Object bean : beans.values()){
MsgType msgType = bean.getClass().getAnnotation(MsgType.class);
HandleFactory.registerHandleService(msgType.value(),(HandleService) bean);
}
}
}
contextRefreshedEvent为上下文刷新事件,实现了这个接口,Spring容器加载完毕后,就会执行这个实现类重写的方法
这样,当ioc容器加载处理完相应的bean之后,也给我们提供了一个机会(先有InitializingBean,后有ApplicationListener),可以去做一些自己想做的事。其实这也就是spring ioc容器给提供的一个扩展的地方。我们可以这样使用这个扩
展机制,首先需要认识几个相关的接口
org.springframework.context.ApplicationEvent
org.springframework.context.ApplicationListener
一个最简单的方式就是,让我们的bean实现ApplicationListener接口,这样当发布事件时,spring的ioc容器就会以容器的实例对象作为事件源类,并从中找到事件的监听者,此时
ApplicationListener接口实例中的onApplicationEvent(E event)方法就会被调用,我们的逻辑代码就会写在此处。这样我们的目的就达到了。但这也带来一个思考,有人可能会
想,这样的代码我们也可以通过实现spring的InitializingBean接口来实现啊,也会被spring容器去自动调用,但是大家应该想到,如果我们现在想做的事,是必须要等到所有的
bean都被处理完成之后再进行,此时InitializingBean接口的实现就不合适了,所以需要深刻理解事件机制的应用场合。
3. redis发布订阅
3.1 需要的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 配置redis以及连接池
# redis
spring:
redis:
host: 127.0.0.1
port: 6379
# password:
database: 1
timeout: 5000
jedis:
pool:
max-active: 8
max-wait: 1
max-idle: 500
min-idle: 0
3.3 创建消息的发布者和消息处理者类
消息发布者
@EnableScheduling//开启定时器功能
@Component
public class MessageSender {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 间隔2秒,通过stringRedisTemplate对象向redis消息队列chat频道发布消息
*/
@Scheduled(fixedDelay = 2000)
public void sendMessage() {
stringRedisTemplate.convertAndSend("chat", String.valueOf(Math.random()));
}
}
消息处理器pojo
@Component
public class MessageReceiver {
/**
* 接收消息方法
*/
public void receiverMessage(String message) {
System.out.println("MessageReceiver收到一条新消息:" + message);
}
}
3.4 设置消息发布者,消息处理者POJO,redis消息监听容器以及redis监听器注入IOC容器
/**
* redis配置
*
* @author 段誉
* @create 2019-03-25 9:59
*/
@Configuration//相当于xml中的beans
public class RedisConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//订阅了一个叫chat的通道
container.addMessageListener(listenerAdapter, new PatternTopic("chat"));
return container;
}
/**
* 消息监听器适配器,绑定消息处理器,利用反射技术调用消息处理器的业务方法
* @param receiver
* @return
*/
@Bean
MessageListenerAdapter listenerAdapter(MessageReceiver receiver) {
//给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
//不填defaultListenerMethod默认调用handleMessage
return new MessageListenerAdapter(receiver, "receiverMessage");
}
/**
* 读取内容的template
*/
@Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
4. Prometheus+Grafana监控
4.1 依赖内容
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.1.3</version>
</dependency>
</dependencies>
4.2 配置文件
配置文件中加入配置,这里就只进行一些简单配置,management.metrics.tags.application属性是本文配合Grafana的Dashboard设置的
spring.application.name=springboot_prometheus
management.endpoints.web.exposure.include=*
management.metrics.tags.application=${spring.application.name}
4.3设置application
修改启动类
@SpringBootApplication
public class Springboot2PrometheusApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot2PrometheusApplication.class, args);
}
@Bean
MeterRegistryCustomizer<MeterRegistry> configurer(
@Value("${spring.application.name}") String applicationName) {
return (registry) -> registry.config().commonTags("application", applicationName);
}
}
访问http://localhost:8080/actuator/prometheus
4.4 Prometheus配置
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['127.0.0.1:9090']
###以下内容为SpringBoot应用配置
- job_name: 'springboot_prometheus'
scrape_interval: 5s
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['127.0.0.1:8080']
4.5 启动Prometheus
查看Prometheus监控的应用
target按钮
列表中UP的页面为存活的实例
也可以查看很多指数
4.6 Grafana配置
从官网上下载并启动
sudo service grafana-server start
启动服务 访问http://localhost:3000
添加数据源
4.7 Grafana图表大概参数解析
首先添加一个图表
这里变量应该监控项可以使用/.*/以表示应用集所有的变量,Grafana还可以使用正则来表示某些参数,比如test[\s]或test[^0,1,2].*等等,这些可以根据需求来做一些相应的调整