@TOC
前言
上一篇点击查看使用xml来实现自定义IOC以及依赖注入关系维护,动态代理,以及事务操作;
这次使用注解来实现IOC以及依赖关系维护
步骤以及思路分析
基于xml实现方式时,仅仅只需要在xml里面配置好bean的id以及bean的权限定命名,然后反射实例化对象,最后加入到ioc容器中
依赖注入时候仅仅需要获取property
标签以及父级标签,根据property
名从ioc容器中获取到需要注入的bean示例即可;
如果是基于注解实现呢!
- 1 首先需要自定义注解
- 2 如何获取自定义到的注解
- 3 实例化打了注解的实例
- 4 在指定bean成员变量上是否包含需要注入的注解,然后依赖注入
- 5 生成代理对象,基于接口判断是否选择JDK动态代理或者CGLIB代理
代码实现
首先自定义注解
实例bean的注解 @Repository
和@Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Repository {
String value() default "";
}
自动装配的注解
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String name() default "";
}
事务注解 Transactional
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Transactional {
}
然后 在类上标注注解,以及依赖注入
项目结构
配置信息
版本 JDK8 , tomcat 7 , IDEA 2019 03
所需依赖
javax.servlet
javax.servlet-api
3.1.0
provided
cglib
cglib
2.1_2
tomcat插件
org.apache.tomcat.maven
tomcat7-maven-plugin
2.2
8080
/
这里我们使用一个StartApplication
类来表示当前的顶级包下的启动类,相当于SpringBoot里的Main方法所在的类(目的仅仅是指定包,也可以在xml里面配置包名)
在Web.xml里面进行配置一下这个启动类
tomcat启动时候启动IOC容器
com.udeam.edu.factory.impl.AnnotationBeanFactory
定义BeanFactory
接口类
/**
* 底层BeanFactory工厂接口
* @author Pilgrim
*/
public interface BeanFactory {
/**
* 存储bean单例
*/
public final static Map IOC_MAP = new HashMap<>();
/**
* 对外提供获取bean接口
*
* @param id
* @return bean对象
*/
public Object getBean(String id);
/**
* 根据类型对外提供获取bean示例
*
* @param classz
* @return bean
*/
public Object getBean(Class> classz);
/**
* 获取容器中所有的bean名字
*
* @return
*/
public Object getAllBeanName();
}
抽象类AbstractBeanFactory
扩展一些属性
public abstract class AbstractBeanFactory implements BeanFactory {
/**
* 存储bean单例
*/
public final static Map IOC_MAP = new HashMap<>();
/**
* 容器执行一次 标识
*/
public static boolean isTag = false;
public static final String CLASS_STR = ".class";
}
然后编写bean工厂实现类AnnotationBeanFactory
定义初始化方法
initBeanFactory(String packageName);
初始化方法包含以下方法
文件扫描路径
/**
* 递归处理路径下文件夹是否包含文件夹,如不包含则获取当前类的权限定命名存入set中
*
* @param packName
* @param classNameSet
* @param path
*/
public static void parseFilePackName(String packName, Set classNameSet, String path)
bean实例化方法
private void setBean();
bean依赖注入方法
beanAutoWired()
事务处理注解方法
doScanTransactional()
/**
* 注解方式 实现 Bean工厂
*
* @author Pilgrim
*/
public class AnnotationBeanFactory extends AbstractBeanFactory {
/**
* 2 注解 + 扫描包 方式实现 ioc 容器
* tomcat启动的时候去初始化容器
*/
public AnnotationBeanFactory() {
if (isTag) {
return;
}
try {
String packageName = StartApplication.class.getPackage().getName();
//扫描启动类的包名
System.out.println("------------------- [容器]正在初始化 ------------ ");
System.out.println(String.format("------------------- 扫描当前包是%s ------------ ", packageName));
initBeanFactory(packageName);
System.out.println("------------------- [容器]初始化完成 ------------ ");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
isTag = true;
}
}
包扫描
递归扫描包下的文件
com.xxx.xxx 包名需要转换成磁盘目录 c:/xxx/xx这样的形式
获取包名
String packageName = StartApplication.class.getPackage().getName();
转换包名以及扫描包得到所有的class文件名
if (Objects.isNull(packName) || packName.length() == 0) {
throw new RuntimeException("无效的包路径");
}
packName = packName.replace(".", File.separator);
URL resource = AnnotationBeanFactory.class.getClassLoader().getResource(packName);
String path = resource.getPath();
//解析中文
String filePath = URLDecoder.decode(path, "UTF-8");
递归处理
/**
* 递归处理路径下文件夹是否包含文件夹,如不包含则获取当前类的权限定命名存入set中
*
* @param packName
* @param classNameSet
* @param path
*/
public static void parseFilePackName(String packName, Set classNameSet, String path) {
File packNamePath = new File(path);
if (!packNamePath.isDirectory() || !packNamePath.exists()) {
return;
}
//递归路径下所有文件和文件夹
for (File file : packNamePath.listFiles()) {
boolean directory = file.isDirectory();
String classNamePath = packName + File.separator + file.getName().replace(File.separator, ".");
if (directory) {
parseFilePackName(classNamePath, classNameSet, file.getPath());
} else if (file.isFile() && file.getName().endsWith(CLASS_STR)) {
//存入set
classNameSet.add(classNamePath.replace(File.separator, ".").replace(CLASS_STR, ""));
}
}
}
bean实例化
得到所有的java文件名,然后去实例化bean
判断是否包含我们刚才自定义的注解
private void setBean() {
stringSet.forEach(x -> {
try {
//排除指定包 servlet 类不能被实例化 这儿排除
if (!x.contains("servlet")) {
Class> aClassz = Class.forName(x);
serviceAnnotation(aClassz);
repositoryAnnotation(aClassz);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
});
}
判断是否含有Service
和Repository
注解
获取bean的名字 并且判断当前类是否有 Service 注解,如有则存入Ioc 如包含属性value不为空,则设置value属性为bean的key
public void serviceAnnotation(Class aClass1) throws InstantiationException, IllegalAccessException {
Service annotation = (Service) aClass1.getAnnotation(Service.class);
if (Objects.nonNull(annotation)) {
setIocNameMap(annotation.value(), aClass1.getSimpleName(), aClass1);
}
}
Repository 同理
public void repositoryAnnotation(Class aClass) throws InstantiationException, IllegalAccessException {
Repository annotation = (Repository) aClass.getAnnotation(Repository.class);
if (Objects.nonNull(annotation)) {
setIocNameMap(annotation.value(), aClass.getSimpleName(), aClass);
}
}
获取bean的name setIocNameMap
方法然后实例化bean加入到容器
这儿判断一下是否是单例bean 这儿的单例指的是是否已经有一个bean了
public void setIocNameMap(String value, String className, Class clasz) throws IllegalAccessException, InstantiationException {
String iocNameString = value;
Object beanDefinition = clasz.newInstance() ;
if (value.length() > 0) {
if (IOC_MAP.containsKey(value)) {
throw new RuntimeException("the named" + className + ", had one ... ");
}
} else {
//默认设置bean首字母小写的
iocNameString = getIocNameString(className);
if (IOC_MAP.containsKey(iocNameString)) {
throw new RuntimeException("the named " + className + ", had one ... ");
}
}
// 根据父接口类型注入
Class>[] interfaces = clasz.getInterfaces();
if (interfaces != null) {
for (Class> anInterface : interfaces) {
IOC_MAP.put(anInterface.getSimpleName(), beanDefinition);
}
}
IOC_MAP.put(iocNameString, beanDefinition);
}
设置首字母小写
public static String getIocNameString(String className) {
return (String.valueOf(className.toCharArray()[0])).toLowerCase() + className.substring(1, className.length());
}
依赖注入
依赖注入方法,获取成员变量上有 Autowired 注解的字段,然后根据当前类类型去自动装配
public static void beanAutoWired() throws ClassNotFoundException {
//获取成员变量上有 Autowired 注解的字段,然后根据当前类类型去自动装配
for (Map.Entry stringObjectEntry : IOC_MAP.entrySet()) {
Object beanDefinition = stringObjectEntry.getValue();
Class> aClass = beanDefinition.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
if (Objects.isNull(declaredFields) && declaredFields.length == 0) {
continue;
}
for (Field field : declaredFields) {
//字段含有 Autowired 注解的需要被自动装配对象
Autowired autowired = field.getAnnotation(Autowired.class);
if (Objects.nonNull(autowired)) {
//根据当前key获取需要注入示例对象
//先根据名字注入,如果名字获取不到,再根据类型去注入
String beanName = autowired.name();
if (StringUtils.isEmpty(beanName)) {
beanName = field.getType().getSimpleName();
}
//反射设置值
try {
field.setAccessible(true);
//自动装配 线程不安全,Spring中默认单例
field.set(stringObjectEntry.getValue(), IOC_MAP.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
扫描事务注解
public void doScanTransactional() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
for (Map.Entry classBeanDefinitionEntry : IOC_MAP.entrySet()) {
Object beanDefinition = classBeanDefinitionEntry.getValue();
//判断生成代理对象
Object proxy = getProxy(beanDefinition);
if (proxy==null){
proxy = beanDefinition;
}
//更新bean
IOC_MAP.put(classBeanDefinitionEntry.getKey(), proxy);
}
}
判断选择哪个代理实现方式 根据是否实现接口
public Object getProxy(Object aClass) {
Object jdkProxy = null;
Transactional annotation = aClass.getClass().getDeclaredAnnotation(Transactional.class);
if (Objects.nonNull(annotation)) {
//有接口使用jdk动态代理
if (aClass.getClass().getInterfaces() == null || aClass.getClass().getInterfaces().length <= 0) {
//cglib动态代理
jdkProxy = ProxyFactory.getCglibProxy(aClass);
} else {
/*for (Class anInterface : aClass.getClass().getInterfaces()) {
System.out.println(anInterface.getSimpleName());
}*/
jdkProxy = ProxyFactory.getJdkProxy(aClass);
}
}
return jdkProxy;
}
代理对象实现 以及方法执行前后处理事务
/**
* 代理类工厂
*
* @author Pilgrim
*/
public class ProxyFactory {
/**
* 事务管理器
*/
private final TransferServiceManager t = TransferServiceManager.get();
/**
* Jdk动态代理
*
* @param obj 被代理的对象
* @return 返回代理对象
*/
public static Object getJdkProxy(Object obj) {
Object o = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Object invoke = null;
try {
// 开启事务(关闭事务的自动提交)
TransferServiceManager.get().start();
invoke = method.invoke(obj, objects);
// 提交事务
TransferServiceManager.get().commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
TransferServiceManager.get().rowback();
throw e;
}
return invoke;
}
});
return o;
}
/**
* cglib动态代理
*
* @param object 被代理的对象
* @return 返回代理对象
*/
public static Object getCglibProxy(Object object) {
//生成代理对象
return Enhancer.create(object.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try {
//开启事务
TransferServiceManager.get().start();
result = method.invoke(object, objects);
//提交事务
TransferServiceManager.get().commit();
} catch (Exception e) {
//回滚事务
TransferServiceManager.get().rowback();
throw e;
}
return result;
}
});
}
初始化方法步骤
/**
* 初始化bean
* 1 递归扫描包获取类权限定命名
* 2 实例化bean
* 3 依赖注入
* 4 扫描事务注解 生成代理对象
*
* @param packName
* @throws UnsupportedEncodingException
*/
public void initBeanFactory(String packName) throws UnsupportedEncodingException, InstantiationException, IllegalAccessException, ClassNotFoundException {
if (Objects.isNull(packName) || packName.length() == 0) {
throw new RuntimeException("无效的包路径");
}
packName = packName.replace(".", File.separator);
URL resource = AnnotationBeanFactory.class.getClassLoader().getResource(packName);
String path = resource.getPath();
//解析中文
String filePath = URLDecoder.decode(path, "UTF-8");
//解析包成java权限定命名com
parseFilePackName(packName, stringSet, filePath);
//实例化bean
setBean();
//System.out.println(String.format("获取到的bean : %s ", IOC_MAP));
//自动装配
beanAutoWired();
//扫描事务注解
doScanTransactional();
}
对外提供getBean(方法)
实现getBean方法
@Override
//根据id名获取
public Object getBean(String id) {
if (Objects.nonNull(id) && id.length() > 0) {
Object beanDefinition = IOC_MAP.get(id);
return beanDefinition;
}
return null;
}
@Override
//根据类型获取
public Object getBean(Class> aClass) {
if (Objects.isNull(aClass)) {
return null;
}
return IOC_MAP.get(aClass.getSimpleName());
}
@Override
public Object getAllBeanName() {
return IOC_MAP.keySet();
}
测试
启动tomcat可以看到启动成功,bean实例化完成
在servlet init方法里可以调用看一下
private TransferService transferService ;
@Override
public void init() throws ServletException {
BeanFactory beanFactory = new AnnotationBeanFactory();
transferService= (TransferService)beanFactory.getBean("transferServiceImpl");
TransferService transferService2 = (TransferService) beanFactory.getBean(TransferService.class);
super.init();
}
debug可以看到 根据类型还是id都可以获取到代理之后的bean