自定义@Service、@Autowired、@Transactional注解类和声明式事物
自定义@Service、@Autowired、@Transactional注解类,完成基于注解的IOC容器(Bean对象创建及依赖注入维护)和声明式事务控制,写到转账工程中,并且可以实现转账成功和转账异常时事务回滚
- 首先对注解类进行开发:
写自定义注解的代码,在写之前需要先了解以下两个元注解:
@Target:
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
作用:被描述注解的使用范围:
取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention:
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自Java.lang.annotation.RetentionPolicy的枚举类型值。
以下是本项目对注解类进行开发:
注解的声明周期采用的都是:RUNTIME,运行时有效
@Autowired的注解使用范围是FIELD:用于描述域
@Service和@Transactional的注解使用范围是TYPE:用于描述类
- 将xml中声明的bean转为注解的形式
比如TransferServiceImpl:
再来解释以**解反射实现部分:
总共分三步:
- 扫描包下所有的@service,通过反射完成对象实例化
- 根据@Autowired完成注解的依赖关系
- 针对@Transactional,修改对象为对应的代理对象
/**
* 声明静态方法,一开始就加载
* 方法一: 扫描注解解析
*/
static {
// 任务一:扫描包,通过反射技术实例化对象并且存储待用(map集合)
try{
//扫描获取反射对象集合
Reflections reflections = new Reflections("com.lagou.edu");
Set<Class<?>> servecesTypesAnnotatedWith = reflections.getTypesAnnotatedWith(Service.class);
for (Class<?> c : servecesTypesAnnotatedWith) {
// 通过反射技术实例化对象
Object bean = c.newInstance();
Service annotation = c.getAnnotation(Service.class);
//对象ID在service注解有value时用value,没有时用类名
if(StringUtils.isEmpty(annotation.value())){
//由于getName获取的是全限定类名,所以要分割去掉前面包名部分
String[] names = c.getName().split("\\.");
map.put(names[names.length-1], bean);
}else{
map.put(annotation.value(), bean);
}
}
// 实例化完成之后维护对象的依赖关系Autowired,检查哪些对象需要传值进入,
for(Map.Entry<String, Object> a: map.entrySet()){
Object o = a.getValue();
Class c = o.getClass();
//获取所有的变量
Field[] fields = c.getDeclaredFields();
//遍历属性,若持有Autowired注解则注入
for (Field field : fields) {
//判断是否是使用注解的参数
if (field.isAnnotationPresent(Autowired.class)
&&field.getAnnotation(Autowired.class).required()) {
String[] names = field.getType().getName().split("\\.");
String name = names[names.length-1];
//Autowired注解的位置需要set方法,方便c.getMethods()获取
Method[] methods = c.getMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if(method.getName().equalsIgnoreCase("set" + name)) { // 该方法就是 setAccountDao(AccountDao accountDao)
method.invoke(o,map.get(name));
}
}
}
}
//判断对象类是否持有Transactional注解,若有则修改对象为代理对象
if(c.isAnnotationPresent(Transactional.class)){
//获取代理工厂
ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
Class[] face = c.getInterfaces();//获取类c实现的所有接口
//判断对象是否实现接口
if(face!=null&&face.length>0){
//实现使用JDK
o = proxyFactory.getJdkProxy(o);
}else{
//没实现使用CGLIB
o = proxyFactory.getCglibProxy(o);
}
}
// 把处理之后的object重新放到map中
map.put(a.getKey(),o);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
最后演示**解成果:
- 演示前先看下表中数据:
- 李向韩划款1000,操作成功
数据库显示
- 李向韩划款1000,操作失败
先调整代码:
操作失败:
数据库显示
说明转账异常时事务回滚成功。