前言
本文在创建类的实例时,是直接操作的属性,而没有用setter或者constructor方法,这是不合理的(违背了JavaBean封装的思想)。故建议在阅读本文时,仅关注实现的思路,不要参考具体实现过程,而应该使用spring的setter/constructor两种方法
IOC控制反转
本文继java的springMvc框架简单实现后,就Service-ServiceImpl
及Dao--DaoImpl
之间的依赖关系进行解耦,简单实现@AutoWired
有关此部分的依赖注入(DI)。
思路
- 以Dao为例,在声明某个Dao时,我们不再赋初值:
@XxgAutoWired
private UserDao userDao;
- 在
UserDaoImpl
类上面加入@XxgComponent
注解
@XxgComponent
public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao {
...
}
- 服务器启动,
3.1 扫描包过程中,加入:判断类中是否有
@XxgComponent
注解,如果有,则加入我们指定的容器3.2 扫描结束之后,给
MethodDefinition
创建所在类的实例。 - 利用反射执行method时,传递上一步创建的所在类的实例。
所在类的实例说明:
主要将该实例中有@AutoWired
注解的属性,赋予初值,每次调用方法都使用该实例,就达到了预想的效果。
赋初值的方式:
Map<Class, ComponentDefinition> componentMap
容器的key存放依赖类实现的接口的class
,value存放该类的相关属性。- 获取到有
@AutoWired
注解的属性时,获取该属性的类型class作为key,在容器中查找该key - 容器中查到的数据,即为其实现类的相关属性
- 将其赋值给该实现类
问题
这种实现模式,有一个最大的问题:
一个Dao的接口,只能有一个对应的实现类。
其次:
依赖注入仅在Controller类中使用了才有效
可以使用xml文件来存储接口与其实现类的依赖关系。
一个接口有多个实现类时,只在需要的实现类上添加@XxgComponent注解
代码实现
1.XxgAutoWired注解
/**
* @Auther dbc
* @Date 2020/10/15 14:40
* @Description
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface XxgAutoWired {
}
2. XxgComponent注解
/**
* @Auther dbc
* @Date 2020/10/16 15:48
* @Description
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface XxgComponent {
}
3.MethodDefinition中添加一个类的实例
private Object parentInstance; // 所属类的实例
4.扫描类的部分修改
添加Component容器
private static Map<Class, ComponentDefinition> componentMap = new ConcurrentHashMap<>(); // component类容器
ComponentDefinition
/**
* @Auther dbc
* @Date 2020/10/16 16:03
* @Description 对有@XxgComponent注解的类 进行包装
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ComponentDefinition {
private Class typeClazz; // 类对象
private String typeName; // 类名
private XxgComponent component; // component注解
private Class implClazz; // 实现的父类
}
修改dealClass方法
private void dealClass(Class clazz) throws Exception {
// 一开始就添加
if (clazz.isAnnotationPresent(XxgComponent.class)) {
// 将实现类添加到容器中
addComponent(clazz);
return ;
}
...
}
将实现类添加到容器中的方法
/**
* 将Component类添加到容器中
*/
private void addComponent(Class clazz) throws Exception {
ComponentDefinition componentDefinition = new ComponentDefinition();
componentDefinition.setComponent((XxgComponent) clazz.getAnnotation(XxgComponent.class));
componentDefinition.setTypeClazz(clazz);
componentDefinition.setImplClazz(clazz.getInterfaces()[0]);
componentDefinition.setTypeName(clazz.getName());
if (componentMap.get(componentDefinition.getImplClazz()) != null) {
throw new Exception("已存在相同的实现类" + componentDefinition.getImplClazz().getName());
}
componentMap.put(componentDefinition.getImplClazz(), componentDefinition);
}
/**
* 处理controller的属性,自动注入值, 返回该类的一个实例
*/
private Object dealClassField(Class clazz) {
Field[] fields = clazz.getDeclaredFields();
Object instance = null;
try {
instance = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
for(Field field : fields) {
if (field.isAnnotationPresent(XxgAutoWired.class)) {
// 给有XxgAutoWired注解的属性,自动注入值
field.setAccessible(true);
try {
ComponentDefinition componentDefinition = componentMap.get(field.getType());
if (componentDefinition != null) {
// 处理@XxgComponent 的自动注入
field.set(instance, dealClassField(componentDefinition.getTypeClazz()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return instance;
}
5.调用方法时,传递扫描时存入的实例
在DispatcherServlet
中,当获取到请求执行的方法,以及装配好参数之后,调用该方法时,传递创建好的实例
try {
Object result = methodDefinition.getMethod().invoke(
methodDefinition.getParentInstance(), // 所属类的实例
params.toArray());
sendResponse(result, req, resp);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
sendResponse(e.getMessage(), req, resp);
}
后续
真正的@AutoWired
太强大了,可以用在属性上,还可以用在方法上等,博主就不再深究了。
下文博主将使用XML配置文件的方式实现依赖注入,并对单例模式、依赖的值类型等进行处理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。