1. Java反射
Java反射机制存在于运行时,对于任意一个类,都能获取这个类的所有属性和方法,对于任意一个对象,都能通过反射机制去调用它的属性和方法,这种动态获取类信息及动态调用对象方法的功能称为Java反射机制,即在运行时获取Java类的相关信息。
2. 反射
2.1 实体类Student
public class Student {
private String name;
private Integer age;
private String sex;
public Student() {
}
public Student(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
2.2 测试类StudentTest
2.2.1 获取Class对象
获取Class对象总共有三种方法:
- 通过类名.class获取;
- 通过实例名.getClass()获取;
- 通过Class.forName(“全路径”)进行获取;
示例代码:
public void studentTest() throws Exception {
/**
* 获取Student类有三种方法
* 1. 通过类名.class获取 Student.class
* 2. 通过对象名.getClass()获取 new Student().getClass()
* 3. 获取Class.forName("全路径")获取
*/
//1
Class clazz1 = Student.class;
//2
Class clazz2 = new Student().getClass();
//3
Class clazz3 = Class.forName("pers.liyongxing.reflect.Student");
//实例化
Student car = (Student) clazz3.getDeclaredConstructor().newInstance();
}
2.2.2 获取构造方法
获取构造方法分为两种,分别为通过getConstructors和getDeclaredConstructors来获取。
- getConstructors方法是获取所有公共的构造方法
- getDeclaredConstructors方法是获取所有的构造方法
示例代码:
public void constructorTest() throws Exception {
Class clazz = Student.class;
//1. 获取无参数的构造方法
Constructor[] constructors = clazz.getConstructors();
Constructor[] constructors1 = clazz.getDeclaredConstructors();
for(Constructor constructor : constructors){
System.out.println("构造类型:" + constructor.getName() + ",构造参数个数:" + constructor.getParameterCount());
}
//2. 获取有参数的构造方法并实例化 公共构造方法
Student student = (Student)clazz.getConstructor(String.class, Integer.class, String.class).
newInstance("张三", 18, "男");
System.out.println(student);
//2. 获取有参数的构造方法并实例化 私有构造方法
Constructor c2 = clazz.getDeclaredConstructor(String.class, Integer.class);
c2.setAccessible(true);//允许方法私有构造方法
Student student1 = (Student) c2.newInstance("张华", 23);
System.out.println(student1);
}
2.2.3 获取属性和方法
和上面情况类似,如果是私有方法,需要通过declared*方法进行获取,并对相应的属性或者方法设置允许访问(setAccessible(true));
获取属性示例代码:
public void attributeTest() throws Exception{
Class clazz = Student.class;
Student student = (Student) clazz.getDeclaredConstructor().newInstance();
//获取属性
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
if(field.getName().equals("name")){
field.setAccessible(true);
field.set(student,"李四");
}
System.out.println(field.getName());
System.out.println(student);
}
}
获取方法并通过执行invoke()方法执行示例代码:
public void methodTest() throws Exception{
Class clazz = Student.class;
Student student = (Student) clazz.getDeclaredConstructor().newInstance();
Method[] methods = clazz.getDeclaredMethods();
for(Method method : methods){
if(method.getName().equals("toString")){
String invoke = (String)method.invoke(student);
System.out.println("toString方法执行了.." + invoke);
}
}
}
3 手写IOC(简单版)
自定义Bean,主要分为两个:类型注入和属性注入:
3.1 自定义Bean
- 类型Bean
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
- 属性Bean
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
3.2 定义测试类
- UserService:
public interface UserService {
public void add();
}
- UserServiceImpl
@Bean
public class UserServiceImpl implements UserService {
@Di
private UserDao userDao;
@Override
public void add() {
System.out.println("service.....");
userDao.add();
}
}
- UserDao
public interface UserDao {
public void add();
}
- UserDaoImpl
@Bean
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("add...");
}
}
- 测试类AnnotationTest
public void test(){
ApplicationContext context = new AnnotationApplicationContext("pers.liyongxing");
UserService userService = (UserService) context.getBean(UserService.class);
System.out.println(userService);
userService.add();
}
3.3 自定义ApplicationContext
自定义ApplicationContext中的AnnotationApplicationContext,实现对路径中的注解进行扫描,如果为类型注入,则将通过反射机制创建该类的对象,并将其存储到Map集合中,最后遍历Map集合,如果当前对象中存在Di属性注入,则将该属性注入其对应的类型。
3.3.1 自定义ApplicationContext接口:
public interface ApplicationContext {
Object getBean(Class clazz);
}
3.3.2 创建AnnotationApplicationContext类:
创建AnnotationApplicationContext类并实现ApplicationContext接口;
属性:
beanFactory用于存储类型注入的bean对象;rootPath用于存储文件路径的公共部分。
private static Map<Class,Object> beanFactory = new HashMap<>();
private static String rootPath;
- 格式化处理包路径,并解析出包路径下的所有文件及文件夹,用于后面的类型注入
public AnnotationApplicationContext(String basePackage){
//格式化路径
//1. 把.换成/
String packagePath = basePackage.replaceAll("\\.", "\\\\");
//2. 获取当前包的绝对路径
try {
Enumeration<URL> urls =
Thread.currentThread().getContextClassLoader().getResources(packagePath);
while(urls.hasMoreElements()){
URL url = urls.nextElement();
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
//获取包前面的路径部分
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
//包扫描
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
//属性注入
loadDi();
}
注意下面操作的文件路径为target目录下:
2. 类型注入:
- 首先判断当前File对象是否为文件夹,如果是文件夹则进行遍历文件判断,如果不是则返回;
- 如果当前文件夹为空,直接返回;
- 遍历当前文件夹下的所有File对象,如果当前File仍然为文件夹,则返回第二步进行执行,如果不是,则进行下一步。
- 截取掉文件头的路径,获取相对于包的绝对路径。
- 判断当前文件是否为.class文件,如果是.class对象,进行下一步
- 格式化处理类路径,通过Class.forName(“路径”)获取Class对象,通过isInterface()判断当前对象是否为接口类型,如果当前对象不是接口类型,则进行下一步。
- 判断当前对象是否包含Bean注解,如果包含,进行实例化,并将实例化对象存储到beanFactory集合中:
7.1如果当前类实现接口,则key为接口类型,value为实例化对象。
7.2如果当前类没有实现接口,则key为class对象,value为实例化对象.
示例代码:
private static void loadBean(File file) throws Exception {
//1. 判断当前内容是否为文件夹
if(!file.isDirectory()){
return ;
}
//2 获取文件夹中的内容
File[] childrenFiles = file.listFiles();
//3 判断文件夹为空 直接返回
if(childrenFiles == null || childrenFiles.length == 0){
return ;
}
//4 文件夹不为空,遍历文件夹所有内容
for(File childFile : childrenFiles){
//4.1 判断得到每个File对象,继续判断,如果还是文件夹,继续遍历
if(childFile.isDirectory()){
//继续遍历
loadBean(childFile);
}else{
//4.2 遍历得到File对象不是文件夹,是文件,
//4.3 得到包路径+类名称-字符串截取
String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
//4.4 判断当前文件类型是否为.class
if(pathWithClass.contains(".class")){
//4.5 如果是.class类型,把路径中\换成. 把.class去掉
String pathName = pathWithClass.replaceAll("\\\\", ".")
.replace(".class","");
//4.6 判断类上是否有注解@Bean,如果有,则进行实例化
//4.6.1 获取类的class对象
Class<?> clazz = Class.forName(pathName);
//4.6.2 判断是否为接口
if(!clazz.isInterface()){
//4.6.3判断是否有注解
Bean annotation = clazz.getAnnotation(Bean.class);
if(annotation != null){
//4.6.4 进行实例化
Object instance = clazz.getDeclaredConstructor().newInstance();
//4.7 实例化之后,将实例化的bean放到map中
//4.7.1 如果当前类有接口,就让接口的class作为key
if(clazz.getInterfaces().length > 0){
beanFactory.put(clazz.getInterfaces()[0],instance);
}else{
beanFactory.put(clazz,instance);
}
}
}
}
}
}
}
- 属性注入
属性注入,主要判断beanFactory中的class对象是否包含Di注解的属性,如果包含则将属性通过字段类型进行注入,
示例代码:
private void loadDi(){
//1. 实例化对象都在beanFactoryMap集合中,
if(beanFactory.isEmpty()){
return;
}
//1. 遍历map集合
for (Map.Entry<Class,Object> entry: beanFactory.entrySet()){
//2. 获取map集合中的每个对象(value),每个对象属性获取到
Object obj = entry.getValue();
//获取对象的class部分
Class<?> clazz = obj.getClass();
//3 遍历得到每个对象属性的数组,得到每个属性
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
Di annotation = field.getAnnotation(Di.class);
if(annotation != null){
//4 判断属性上面是否有Di注解
field.setAccessible(true);
//5 如果有Di注解 将对象进行注入
try {
field.set(obj,beanFactory.get(field.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
总结
总体来说,理解的比较简单,具体的IOC实现原理还需要看源码多加理解。