使用注解和反射实现SpringIoC容器(简洁版)
首先明确一下自己的需求:我们需要自定义一些注解,比如类似于Spring的Bean或者Configuration注解,Bean注解主要是放在方法上的,目的是将当前方法的返回值放入SpringIoC容器中,Configuration主要是用于识别当前类是一个配置类的,注解定义好之后,就是利用反射实现,首先我们需要定义一个类,这个类可以识别Configuration、Bean之类的注解,并且可以获取加了@Bean注解的方法,执行方法,将方法的返回值加入到IoC容器中。
代码实现:
1、实体类
public class User {
private Integer id ;
private String name ;
private Integer age ;
public User(Integer id ,String name, Integer age) {
this.id = id ;
this.name = name;
this.age = age;
}
public User(){;}
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 Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
2、业务层代码
public interface UserService {
boolean add(User user) ;
User modify(int id ) ;
}
public class UserServiceImpl implements UserService {
@Override
public boolean add(User user) {
System.out.println("添加成功");
return false;
}
@Override
public User modify(int id) {
System.out.println("修改成功");
return null;
}
}
@MyRepository
public class UserServiceImpl2 implements UserService {
@Override
public boolean add(User user) {
return false;
}
@Override
public User modify(int id) {
return null;
}
}
3、注解annotation包
/**
* 当前注解放在方法上,表示根据方法的返回值生成一个bean对象,并放在ioc容器中
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyBean {
String value() default "" ;
}
/**
* 放在类上,表示当前类是一个配置类
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyConfiguration {
}
/**
* 注解放在类上,可生成当前类的对象,并放在ioc容器中
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyRepository {
String value() default "" ;
}
4、配置类
/**
* 表示当前类是一个配置类
*/
@MyConfiguration
public class ClassConfig {
@MyBean
public User getUser(){
return new User() ;
}
@MyBean
public UserService getUserService(){
return new UserServiceImpl() ;
}
}
5、最后一步,就是要利用反射实现注解的功能:MyApplicationContext
这里有两个最主要的功能,一个是实现@Bean注解,另一个是实现@MyRepository注解。
(1)@Bean:这里主要的思路是:
第一步:找到配置类(也即类上有@MyConfiguration注解的类),这里在初始化的时候就可以给出了。
第二步:识别配置类上的每一个加了@MyBean注解的方法,将所有方法添加到map容器中。
第三步:执行map容器中的每一个方法,生成配置类中的所有bean
(2)@MyRepoitory:这里的主要思路是:
第一步:首先需要找到@MyResporitory标注的类,这里就需要扫描包了。
第二步:找到某一包下所有标注了@MyRepository的类并返回。
第三步:根据全限定类名生成对应的对象,并放入IoC容器中。
public class MyApplicationContext {
private Map<String,Method> beanMethods = new HashMap<>(); // 配置类里面的有MyBean注解的方法
private Map<String,Object> beans = new HashMap<>() ; // 表示IoC容器里面装的bean
private Class<?> configClass ;// 配置类
public MyApplicationContext(){;}
/**
* 构造函数,初始化MyApplicationContext里面的配置类
* @param configClass
*/
public MyApplicationContext(Class<?> configClass) throws Exception {
// 首先需要判断这是否时一个配置类,也即是否有configuration注解
if(isMyConfiguration(configClass)){
this.configClass = configClass ;
//System.out.println(this.configClass);
this.produceBeans() ;
}else{
System.out.println("当前类不是配置类");
}
}
public MyApplicationContext(String packageName) throws NoSuchMethodException, IOException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
if (!packageName.isEmpty()){
produceBeansByRepository(packageName) ;
}
}
/**
* 判断当前类是否是一个配置类
* @return
*/
public boolean isMyConfiguration(Class<?> clazz) throws Exception {
boolean flag = false ;
flag = clazz.isAnnotationPresent(MyConfiguration.class) ; // 底层也是使用了getAnnotation
if(!flag){
throw new Exception("配置文件缺少:@MyConfiguration注解") ;
}
return true ;
// Annotation annotation = clazz.getAnnotation(MyConfiguration.class) ;
// if(annotation!=null){
// return true ;
// }
// return false ;
}
/**
* 获取自动配置类里面的方法
* @return
*/
private Map<String,Method> getMethods(){
Method[] methods = configClass.getDeclaredMethods() ;// 获取当前配置类的所有方法
for(Method method:methods){
MyBean beanAnno = method.getAnnotation(MyBean.class) ;
if(null!=beanAnno){ // 当方法上有MyBean注解的时候,
String methodName = "" ;
if(beanAnno.value().equals("")){
// 如果MyBean注解上的value值为默认值,那么将方法名作为bean的名称
methodName = method.getName() ;
}else{
methodName = beanAnno.value() ;
}
//System.out.println(methodName+" "+method);
beanMethods.put(methodName,method) ;
}
}
return beanMethods ;
}
/**
* 获取类的实例,这里调用的是无参构造函数,如果当前类不含有无参构造函数,将抛出异常NoSuchMethodException
* @param <T>
* @return
*/
private <T> T getInstance(Class<T> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return clazz.getDeclaredConstructor(null).newInstance() ;
}
/**
* 生成所有配置类里面的bean
*/
private void produceBeans() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
Map<String,Method> beanMethods = this.getMethods() ;
Set<String> beanNames = beanMethods.keySet() ;// 得到所有bean的名字
Object instance = getInstance(configClass) ;
for(String name:beanNames){
Method method = beanMethods.get(name) ;
beans.put(name,method.invoke(instance)) ;
}
}
/**
* 生成所有通过MyRepository注解添加的bean
*/
private void produceBeansByRepository(String packageName) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
// 第一步,扫描包,获取包下的所有类
List<Class<?>> classList = getClassesByPackage(packageName) ;
String beanName = "" ;
for (Class<?> aClass : classList) {
MyRepository myRepository = aClass.getAnnotation(MyRepository.class) ;
if(myRepository!=null){
if(myRepository.value().equals("")){
beanName = aClass.toString().substring(aClass.toString().lastIndexOf(".")+1,aClass.toString().length()) ;
beanName = beanName.substring(0,1).toLowerCase()+beanName.substring(1,beanName.length());
System.out.println(beanName);
}else {
beanName = myRepository.value() ;
}
}
beans.put(beanName,getInstance(aClass));
}
}
/**
* 获取指定包名下的所有class
* @param packageName
*/
public List<Class<?>> getClassesByPackage(String packageName) throws IOException, ClassNotFoundException {
List<Class<?>> list = new ArrayList<>() ;
String packageDirName = packageName.replace(".","/") ;
// 获取当前包名的运行时的路径
Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()){
URL url = dirs.nextElement() ;
// 将包名路径译码,转换成文件路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
File file = new File(filePath) ;
String[] files = file.list() ;
for(int i=0;i<files.length; i++){
files[i] = files[i].substring(0,files[i].lastIndexOf(".")) ;
Class<?> clazz = Class.forName(packageName+"."+files[i]);
list.add(clazz) ;
}
}
return list;
}
/**
* 通过bean的名称获取容器中的bean
* @param <T>
* @return
*/
public <T> T getBean(String beanName){
Set<String> beanNames = beans.keySet() ;
for(String name:beanNames){
if(name.equals(beanName)){
return (T)beans.get(name) ;
}
}
System.out.println("找不到"+beanName+"对应的Bean");
return null ;
}
}
6、测试类
public class Test {
public static void main(String[] args) throws Exception {
/**
* 配置类的方式生成bean
*/
MyApplicationContext context = new MyApplicationContext(ClassConfig.class) ;
UserService userService = context.getBean("getUserService") ;
System.out.println(userService);
UserService userService1 = context.getBean("userService") ;
System.out.println(userService1);
/**
* 扫描包,通过类上的注解的方式生成bean
*/
MyApplicationContext context1 = new MyApplicationContext("com.example.springioc.ioc.service.impl") ;
UserService userService2 = context.getBean("userServiceImpl2") ;
System.out.println(userService);
}
}
至此:注解和反射实现SpringIoC容器已经实现了,但是还有需要改进和完善的地方,比如控制IoC容器中一个类是单例的还是多例的,或者实现@Autowired自动注入等注解。