今天复习一下反射,说是复习,基本上已经忘干净了,只知道用Spring、Mybatis、JavaFX 的时候加个注解,具体原理就不知道了。所以必须再深入学习一下。
设计一个框架需要什么技术?
反射机制、自定义注解、设计模式、AOP技术、Netty、Spring 架构、SpringBoot 自定义插件、多线程或 JUC。
反射机制的核心是在运行时动态地获取类的信息,并通过这些信息来调用类的成员变量和方法,这种能力使得Java程序可以在运行时动态地加载和执行代码,从而实现更加灵活和动态的功能。
我们以往的学习经历告诉我们,初始化对象的方式无非就是直接 new:
User user = new User();
下面给出两种通过反射初始化对象的方式:
原理:Class 对象的 newInstance 方法会默认执行我们的无参构造方法来初始化对象(所以在定义一个 JavaBean的时候一定要记得写无参构造的方法)。
// 通过反射调用无参构造来初始化对象
Class> aClass = Class.forName("kt.pojo.User");
// newInstance() 默认执行到我们的无参构造方法来初始化对象
User user = (User) aClass.newInstance();
System.out.println(user);
package kt.pojo;
import java.util.Objects;
public class User {
public String name;
public Integer age;
public User(){}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age.equals(user.age) && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
注意:这里的 age 属性类型也可以用 int ,区别就是 int 的默认值为 0,Integer 的默认值为 null。
输出结果:
User{name='null', age=null}
通过我们 Class 对象来获取我们的构造器对象 Constructor,然后调用 Constructor 对象的 newInstacne 方法来实现有参构造。
注意:如果 age 属性类型是 int,那么 getConstructor 的第二个参数应该是 int.class 。
// 通过反射调用有参构造来初始化对象
Class> aClass = Class.forName("kt.pojo.User");
// 通过反射调用有参构造来初始化对象
Constructor> constructor = aClass.getConstructor(String.class, Integer.class);
User tom = (User) constructor.newInstance("tom", 10);
System.out.println(tom);
输出结果:
User{name='tom', age=10}
我们通过反射获取到对象,然后调用对象的方法来赋值(必须保证对象的 setter 方法是 public 的):
User user = (User) aClass.newInstance();
user.setName("tom");
user.setAge(10);
System.out.println(user);
运行结果:
User{name='tom', age=10}
通过反射获取到 Class 对象,再调用 Class 对象的 getDeclareField 方法获得指定属性的属性对象,最后通过属性的 set 方法来实现赋值:
User user = (User) aClass.newInstance();
// aClass.getField(); //这个方法会把我们 User 类的父类(这里是Object)中的属性页获取到
Field name = aClass.getDeclaredField("name");
name.setAccessible(true); // 如果属性是私有的(private) 就需要添加访问权限
Field age = aClass.getDeclaredField("age");
age.setAccessible(true); // 如果属性是私有的(private) 就需要添加访问权限
name.set(user,"tom");
age.set(user,10);
System.out.println(user);
注意:
我们在上面的 User 类中添加一个 sayHello 方法:
public void sayHello(String name){
System.out.println("hello "+name);
}
通过 Class 对象的 getDeclaredMethod 方法获取到指定的方法对象 Method,最后调用 Method 对象的 invoke 方法(需要传入执行该方法的对象和需要的参数):
Class> aClass = Class.forName("kt.pojo.User");
User user = (User)aClass.newInstance();
Method toString = aClass.getDeclaredMethod("sayHello", String.class);
toString.invoke(user,"Tom");
运行结果:
hello Tom
我们需要通过 java 反射机制来实现下面这个接口的实现类:
public interface UserService {
String addUser(String user,int age);
}
public static String createSourceCode(Class classInfo){
//1. 通过反射生成 UserServiceImpl.java
StringBuilder builder = new StringBuilder();
builder.append("package kt.reflect;");
builder.append("public class ").append(classInfo.getSimpleName()).append("Impl implements ").append(classInfo.getSimpleName()).append(" {");
Method[] methods = classInfo.getMethods();
for (Method method : methods) {
builder.append("public ").append(method.getReturnType().getSimpleName()).append(" ").append(method.getName()).append(" (String user, int age) { return \"success\"; }");
}
builder.append("}");
return builder.toString();
}
/**
* 保存代码到本地磁盘
* @param code 源代码
* @param name name.java
*/
public static void saveToLocal(String code,String name) throws IOException {
String fileName = "D:/code/"+name+".java";
File f = new File(fileName);
FileWriter fw = new FileWriter(f);
fw.write(code);
fw.flush();
fw.close();
}
运行测试:
package kt.reflect;public class UserServiceImpl implements UserService {public String addUser (String user, int age) { return "success"; }}
在我们生成的 java 文件的同级目录下生成 class 文件。
/**
* 编译为 class 文件
* @param fileName 完整路径(比如 D:/code//UserServiceImpl.java)
* @throws IOException
*/
public static void compile(String fileName) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable extends JavaFileObject> units = fileMgr.getJavaFileObjects(fileName);
JavaCompiler.CompilationTask t = compiler.getTask(null,fileMgr,null,null,null,units);
t.call();
fileMgr.close();
}
到这里我们都已经有编译好的字节码文件了,直接放到 JVM 内存不就万事大吉了嘛。
我们先编写一个工具类:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @author 刘xx
* @version 1.0
* @date 2023-11-20 18:52
*/
public class JavaClassLoader extends ClassLoader{
private File classPathFile;
public JavaClassLoader(){
// String classPath = JavaClassLoader.class.getResource("").getPath();
String classPath = "D:\\code";
this.classPathFile=new File(classPath);
}
// 根据类名来查找(比如 UserServiceImpl)
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
String className = JavaClassLoader.class.getPackage().getName()+"."+name;
if (classPathFile!=null){
File classFile = new File(classPathFile,name.replaceAll("\\.","/")+".class");
if (classFile.exists()){
FileInputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len;
while ((len=in.read(buff))!=-1){
out.write(buff,0,len);
}
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
e.printStackTrace();
}finally {
if (in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out!=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
}
public static Class> readToMemory() throws ClassNotFoundException {
JavaClassLoader javaClassLoader = new JavaClassLoader();
return javaClassLoader.findClass("UserServiceImpl");
}
我们需要把我们的 接口类 传进去:
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException, IOException {
String s = createSourceCode(UserService.class);
System.out.println(s);
// 将源代码保存到本地磁盘
saveToLocal(s,"UserServiceImpl");
compile("D:/code/UserServiceImpl.java");
Class> aClass = readToMemory();
UserService service = (UserService) aClass.newInstance();
String res = service.addUser("lyh", 20);
System.out.println(res);
}
运行结果:
success
我们在 User 的 sayHello 上定义一个注解:getMapping,并给改注解添加两个参数 name 和 age。
注意:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) //运行时可被反射机制获取
@Target({ElementType.METHOD,ElementType.TYPE}) // 设置该注解只可以被标记在 方法上
public @interface GetMapping {
String name();
int age();
}
@GetMapping(name = "lyh",age = 1)
public void sayHello(String name){
System.out.println("hello "+name);
}
获得注解对象需要使用 getAnnoation 方法。
注意:
Class> aClass = Class.forName("ke.pojo.User");
Method sayHello = aClass.getDeclaredMethod("sayHello", String.class);
// 调用该注解(调用 getAnnotation)的对象必须被该注解标注 才能正确被获取到
GetMapping getMapping = sayHello.getAnnotation(GetMapping.class);
System.out.println(getMapping);
我们看一段使用了 Spring 事务的代码:
@Transactional
public String insertUser(User user){
try{
int res = userMapper.insertUser(user);
int i = 1/0; // 会报错
return "success";
}catch(Exception e){
e.printStackTrace();
return "false";
}
}
代码中,我们使用 @Transactional注解帮助我们自动提交事务和回滚事务。
但是事实上这段代码中的插入语句仍然会被执行,也就是出现了 事务失效的情况,这是因为我们的 try-catch 语句,默认情况下,当事务方法中抛出未检查异常(继承自 RuntimeException 的异常)时,Spring才会标记事务为回滚。在这个例子中,1/0 会抛出 ArithmeticException,这是一个已检查异常。由于我们已经捕获了这个异常,并且没有让它继续抛出,Spring认为事务应该正常提交,而不是回滚。
所以要解决这个问题,就需要我们手动来进行回滚:
@Transactional
public String insertUser(User user){
try{
int res = userMapper.insertUser(user);
int i = 1/0; // 会报错
return "success";
}catch(Exception e){
e.printStackTrace();
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return "false";
}
}
这样,我们的异常才会返回给 AOP ,这样 AOP 才能帮我们进行一个回滚。
注解的使用因为涉及到一些框架的东西,后面用到再来更新。