What are annotations
Annotations are a type of metadata that can be added to Java code. Classes, methods, variables, parameters, and packages can all be annotated. Annotations have no direct effect on the annotated code.
The key that defines the annotation is @interface
Why annotations
Before annotations, XML was widely used to describe metadata. But XML is loosely coupled and cumbersome to maintain. Sometimes it’s more appropriate to use something tightly coupled to your code (like services), annotations emerge, and they’re easier to maintain.
Currently, many frameworks combine XML and Annotation to balance the pros and cons of both approaches. Examples are ButterKnife, EventBus, Retrofit, Dagger, etc
How do annotations work
Annotations are just metadata and have nothing to do with business logic. That is, Annotations simply specify the business logic, and its users do the business logic, and the JVM is its user, working at the bytecode level.
Of course, in the bytecode generation stage of front-end compilation, the compiler has processed annotations. If there are annotation errors, the bytecode cannot be compiled normally. Only after successful compilation generates bytecode. The BUSINESS logic is processed by the JVM at run time.
Yuan notes
Java built-in annotations include Override, Deprecated, SuppressWarnings and so on. A meta-annotation is an annotation that defines an annotation. What it does is define the scope of the annotation, what elements to use, and so on
JDK5.0 provides annotation support for @documented, @retention, @target, and @inherited
Documented: Whether to save to a Javadoc document.
@Retention: Define the lifetime of the annotation. It has three enumerated types: Retentionpolicy. SOURCE(available only in SOURCE), retentionPolicy. CLASS(available in SOURCE and bytecode, annotations used by default), Retentionpolicy.runtime (available in SOURCE code, bytecode, and RUNTIME, and often used for our custom annotations) These annotations are no longer meaningful after compilation, so they are not written to bytecode. @Override and @SuppressWarnings fall into this category
@target: indicates where the annotation is used. If not explicitly stated, the annotation can be placed anywhere. Here are some of the parameters available. Property annotations are compatible and you can add more than one property. Elementtype. TYPE: used to describe a class, interface, or enum declaration elementType. FIELD: used to describe instance variables elementType. METHOD: METHOD elementType. PARAMETER PARAMETER LOCAL_VARIABLE The local variable elementType. ANNOTATION_TYPE Another annotation elementType. PACKAGE Used to record package information for Java files
@Inherited: Whether Inherited is used. The default value is false
The following codes are all developed through Idea
A simple example
Create an annotation class
@Retention(RetentionPolicy.RUNTIME)
public @interface SingleAnno {
String value() default "shy";
}
Copy the code
Refer to it
public class MyClass {
@SingleAnno("single")
public void run() {}}Copy the code
Get the value by reflection
public class TestDemo {
@Test
public void test(){
Class<MyClass> myClass = MyClass.class;
for (Method method : myClass.getDeclaredMethods()){
SingleAnno anno = method.getAnnotation(SingleAnno.class);
if(anno ! = null){ System.out.println(method.getName()); // Print the method name system.out.println (anno.value()); // Print the annotation value}}}}Copy the code
As the console can see, the output is single
run
single
Copy the code
Annotation definition rules
Annotations only support primitive, String, and enumerated types. All properties in annotations are defined as methods and are allowed to provide default values.
Custom annotations and usage
① Define the annotation type (called A), preferably with RUNTIME annotation for Retention. The default should be SOURCE. Provide default values. ③ Add A annotation to other class methods (called M) and assign attribute values to A. ④ Get M methods elsewhere, then get M annotations, and get annotation values, etc.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiAnno {
enum Priority{HIGH,MID,LOW}
enum Status {START,PAUSE,STOP}
String name() default "TheShy";
Priority priority() default Priority.HIGH;
Status status() default Status.START;
}
Copy the code
About the proxy model:
Static proxy:
Core: This is done through aggregation, with the proxy class holding a reference to the delegate class.
A quick example: We used a random sleep time to simulate the train running time. If I want to calculate the running time, and this class can’t be changed.
public interface Runnable {
void running();
}
public class Train implements Runnable {
public void running() {
System.out.println("Train is running......"); int ranTime = new Random().nextInt(1000); try { Thread.sleep(ranTime); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code
There are many solutions: for example, before and after logging where the method is called, inheritance (inheriting Train to call the parent method), aggregation (new Train2, constructor passed in Train object, then calling RUNNING).
But if you add the need to print the log before and after the running method and control the order of execution, you can still do it with inheritance, but continue to create new subclasses, resulting in infinite expansion……
Changing the aggregate to a static proxy solves this problem perfectly by passing the constructor to the Runnable interface:
Public class TrainLogProxy implements Runnable {private Runnable Runnable; public TrainLogProxy(Runnable runnable) { this.runnable = runnable; } public voidrunning() {
System.out.println("Train running start...");
runnable.running();
System.out.println("Train running end..."); }}Copy the code
Public class TrainTimeProxy implements Runnable {private Runnable Runnable; public TrainTimeProxy(Runnable runnable) { this.runnable = runnable; } public voidrunning() {
long start = System.currentTimeMillis();
runnable.running();
long end = System.currentTimeMillis();
System.out.println("run time = "+ (end - start)); }}Copy the code
The following:
Train train = new Train(); // Want to calculate the execution time first, then printlog// TrainTimeProxy trainTimeProxy = new TrainTimeProxy(train); // TrainLogProxy trainLogProxy = new TrainLogProxy(trainTimeProxy); // trainLogProxy.running(); // Want to print firstlogTrainLogProxy TrainLogProxy = new TrainLogProxy(train); TrainTimeProxy trainTimeProxy = new TrainTimeProxy(trainLogProxy); trainTimeProxy.running();Copy the code
The difference between inheritance and aggregation:
Next, look at the class TimeProxy above, where we call the Runable->run() method directly in the fly method. In other words, the TrainTimeProxy actually proxies the Runnable object passed in, which is a typical static proxy implementation. On the face of it, static proxies have solved our problem perfectly. However, imagine if we needed to calculate the runtime of 100 methods in the SDK, the same code would have to be repeated at least 100 times and at least 100 proxy classes would have to be created. To make things smaller, if the Train class has multiple methods, we need to know how long the other methods are running, and the same code needs to be repeated at least several times. Therefore, static proxies have at least two limitations:
- Proxying multiple classes at the same time still results in unrestricted class expansion
- If there are multiple methods in a class, the same logic needs to be implemented repeatedly
Can we use the same proxy class to proxy arbitrary objects? Taking the method runtime as an example, is it possible to use the same class (TrainProxy, for example) to calculate the execution time of any method on any object? Even more boldly, the logic of the agent can be self-specified. For example, the logic of getting the execution time of a method and printing a log can be specified by yourself.
A dynamic proxy
Core Principles:
NewProxyInstance method is used to obtain the instance of the Proxy class, and then the method of the Proxy class can be invoked through the instance of the Proxy class. The invoke method of the mediation class (calling processor) will be invoked when the invoke method is invoked. And you can add your own processing logic.
- Delegate classes: Delegate classes must implement some interface, in this case the Runnable interface.
- Proxy class: Dynamically generated, the newProxyInstance method of the Proxy class is called to get an instance of the Proxy class.
- Mediation classes: The mediation class must implement the InvocationHandler interface as the calling handler “intercepts” the invocation steps of the proxy class methods:
- Proxy->newProxyInstance(infs, handler) is used to generate Proxy objects
- InvocationHandler: This interface is primarily used for custom proxy logic processing
- To complete method interception of the proxied object, we need to pass in the instance of the proxied object in the InvocationHandler object.
Runnable runnable = (Runnable) Proxy.newProxyInstance(Runnable.class.getClassLoader(), new Class[]{Runnable.class}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object invoke = method.invoke(new Train(), args);
System.out.println("after");
returninvoke; }}); runnable.running();Copy the code
We have successfully printed the log before and after running() without modifying the Train class.
The proxy pattern
The biggest characteristic of the proxy mode is that the proxy class and the actual business class implement the same interface (or inherit the same parent class), the proxy object holds a reference to the actual object, the external call is the operation of the proxy object, and in the internal implementation of the proxy object will call the operation of the actual object
Java dynamic proxies are also internally implemented through Java reflection, which means that an object is known and its methods are called dynamically at run time, so that some processing is done before and after the call
Copy write Retrofit
Use dynamic proxy + annotations to perform retrofit effects. The Invoke method of InvocationHandler obtains method, method annotation, method parameter annotation, method parameter and other information, and sets adapter according to the situation to complete the business logic (of course, the adapter action is omitted here).
- The general idea: The Request is dynamically generated with the parameters used in the annotations and then invoked by OKHttp
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Get {
public String value();
}
Copy the code
public interface ServerAPI {
@Get("https://www.baidu.com/")
public String getBaiduHome(@Query("type") String type);
@Post("https://www.baidu.com/update")
public String getBaiduUser(@Field("name") String name, @Field("age") String age);
}
Copy the code
public class APICreater {
public static ServerAPI create(Class<ServerAPI> api){
ServerAPI serverAPI = (ServerAPI) Proxy.newProxyInstance(api.getClassLoader(), new Class[]{api}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
getMethodMsg(method, args);
if ("getBaiduHome".equals(method.getName())) {
return "I am getBaiduHome return by proxy";
}
if ("getBaiduUser".equals(method.getName())) {
return "I am getBaiduUser return by proxy";
}
ServerAPI obj = getAPI();
returnmethod.invoke(obj, args); }});return serverAPI;
}
private static ServerAPI getAPI() {
return new ServerAPI() {
@Override
public String getBaiduHome(String type) {
return null;
}
@Override
public String getBaiduUser(String name, String age) {
returnnull; }}; Private static void getMethodMsg(Method, Method);} // Get the annotation information and parameter information to implement your own custom Method. Object[] args) { AnnoBean bean = new AnnoBean(); bean.setMethodName(method.getName()); Annotation[] annotations = method.getAnnotations();for (Annotation annotation : annotations) {
if (annotation instanceof Get) {
Get getAnni = (Get) annotation;
String value = getAnni.value();
bean.setMethodAnniType("Get");
bean.setMethodAnniValue(value);
}
if (annotation instanceof Post) {
Post getAnni = (Post) annotation;
String value = getAnni.value();
bean.setMethodAnniType("Post");
bean.setMethodAnniValue(value);
}
}
bean.setMethodArgs(Arrays.asList(args));
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation[] annotation : parameterAnnotations) {
for (Annotation annotation1 : annotation) {
if (annotation1 instanceof Field) {
List<String> list = bean.getParamAnniList();
if (list == null) {
list = new ArrayList<String>();
}
list.add("paramAnniType: field " + " value: " + ((Field) annotation1).value());
bean.setParamAnniList(list);
}
if (annotation1 instanceof Query) {
List<String> list = bean.getParamAnniList();
if (list == null) {
list = new ArrayList<String>();
}
list.add("paramAnniType: query " + " value: "+ ((Query) annotation1).value()); bean.setParamAnniList(list); } } } System.out.println(bean.toString()); }}Copy the code
public class TestRetrofitDemo {
@Test
public void testRetrofit(){
ServerAPI serverAPI = APICreater.create(ServerAPI.class);
String homeeeeee = serverAPI.getBaiduHome("Homeeeeee");
System.out.println("-- -- -- -- --"+ homeeeeee); }}Copy the code
Finally, test the output:
AnniBean{methodName='getBaiduHome', methodArgs=[Homeeeeee], methodAnniType='Get', methodAnniValue='https://www.baidu.com/', paramAnniList=[paramAnniType: query value: type]}
-----I am getBaiduHome return by proxy
Copy the code
Github address: github.com/saurylip/An…