1.反射
- 通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
1.1 应用场景
- 注解
- 动态代理
1.2 获取 Class 对象
// 1.知道具体类的情况下可以使用:
Class alunbarClass = TargetObject.class
// 2.通过 `Class.forName()`传入类的路径获取:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
// 3.通过对象实例`instance.getClass()`获取:
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();
// 4.通过类加载器`xxxClassLoader.loadClass()`传入类路径获取:
Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");
//(通过类加载器获取 Class 对象不会进行初始化)
1.3 反射基本操作
package cn.javaguide;
public class TargetObject {
private String value;
public TargetObject() {
value = "JavaGuide";
}
public void publicMethod(String s) {
System.out.println("I love " + s);
}
private void privateMethod() {
System.out.println("value is " + value);
}
}
// 使用反射操作 TargetObject 类的方法以及参数
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/** 获取TargetObject类的Class对象并且创建TargetObject类实例 **/
Class<?> tagetClass = Class.forName("cn.javaguide.TargetObject");
TargetObject targetObject = (TargetObject) tagetClass.newInstance();
/** 获取所有类中所有定义的方法 **/
Method[] methods = tagetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
/** 获取指定方法并调用 **/
Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
String.class);
publicMethod.invoke(targetObject, "JavaGuide");
/*** 获取指定参数并对参数进行修改*/
Field field = tagetClass.getDeclaredField("value");
//为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "JavaGuide");
/*** 调用 private 方法*/
Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
//为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
JAVA controller实现原理
2. 注解
注释是应用于类、接口、方法或字段的元数据,以便向编译器提供有关程序的附加信息
注释可用于将元数据添加到代码中,通常出现在方法或变量定义之前。注释不会修改程序的结构或其行为,相反,它们允许你轻松地在源代码中添加有关元素的上下文信息
2.1 注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
public String info() default "MyAnnotation info"; //default后的是默认值
}
- @Target用来定义注解可用在什么地方,可选值有(同时使用多各值用逗号分隔):
ElementType.Type 类型声明(类,接口,注解,枚举)
ElementType.METHOD 方法声明;
ElementType.FIELD 属性,包括枚举的值;
ElementType.PARAMETER 参数(形参);
ElementType.CONSTRUCTOR 构造器;
ElementType.ANNOTATION_TYPE 注解定义;
ElementType.PACKAGE 包;
ElementType.LOCAL_VARIABLEnd;局部变量声明;
ElementType.TYPE_PARAMETER; 类型参数声明;java 1.8引入
ElementType.TYPE_USE; 类型使用;java 1.8引入
- @Retention 定义在哪一个级别可用,值有:
RetentionPolicy.SOURCE 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
RetentionPolicy.CLASS 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
RetentionPolicy.RUNTIME 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
- @Documented 将此注解包含在javadoc中
- @Inherited 允许子类继承父类的注解
同时Java还内置了三种标准注解
@Override 定义重写超类中的方法,如果拼写错了,或者方法签名对不上被覆盖的方法,编译器会报错
@Depreceted 弃用,如果使用了带有此注解的元素,编译器会发出警告;
@SuppressWarnings 关闭警告信息
2.2 Spring注解处理器
@Aspect
@Component
public class AopAnnotationHandler {
//要处理的切面,此处为要处理的注解MyAnnotation
@Pointcut("@annotation(mypackage.springboot.MyAnnotation)")
public void pointname(){ }
//处理切面前的操作
@Before("pointname()")
public void beforeAno(JoinPoint joinPoint){
System.out.println("befroe pointcut");
Class<?> clazz = joinPoint.getTarget().getClass();
System.out.println(clazz.getAnnotation(MyAnnotation.class).info());
}
//处理切面后的操作
@After("pointname()")
public void afterAno(JoinPoint joinPoint){
try {
System.out.println("after pointcut");
//获取切面方法
Method method = joinPoint.getTarget().getClass()
.getMethod(joinPoint.getSignature().getName(), ((MethodSignature)joinPoint.getSignature()).getParameterTypes());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}}
@Aspect 切面声明,标注在类、接口(包括注解类型)或枚举上,把当前类标识为一个切面供容器读取
@Pointcut 切入点声明,即切入到哪些目标类的目标方法,Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Before 前置通知, 在目标方法(切入点)执行之前执行。value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式。注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知
@Around 环绕增强,相当于MethodInterceptor,目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码
@AfterReturning 后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@AfterThrowing 异常抛出增强,相当于ThrowsAdvice
@After final增强,不管是抛出异常或者正常退出都会执行
3. 动态代理
代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能
静态代理 vs 动态代理
-
灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
-
JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的
3.1. JDK 动态代理机制
1.定义发送短信的接口
public interface SmsService {
String send(String message);
}
2.实现发送短信的接口
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3.定义一个 JDK 动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}
4.获取代理对象的工厂类
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}
5.实际使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
3.2 CGLIB 动态代理
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免
1.实现一个使用阿里云发送短信的类
public interface SmsService {
String send(String message);
}
2.自定义 MethodInterceptor(方法拦截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象(增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}
3.获取代理类
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
4.实际使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");