拉钩java高薪训练营的转账项目,自定义注解完成转账。下面是我代码的改造主要实现思路。
1.整个项目目录,核心改造就是增加自定义注解,改造BeanFactory和ProxyFactory。
2.BeanFactory之前做的事情,主要是从bean.xml获取对象的配置信息,将其解析和装配到Map容器中,现在我们要做的是:
(1)通过反射技术,找到含有@Service注解的类,反射生成其实例,存放到Map容器中。
(2)找到含有@Autowired注解属性,反射获取对象,将其装配到与其对应的类的实例中。
(3)提供一个方法,将类名或者其实现的接口名的首字母小写作为key,在Map容器中找到这个装配好的实例对象。
如下代码:
public class BeanFactory {
/**
* xml配置bean做法
* 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
* 任务二:对外提供获取实例对象的接口(根据id获取)
*/
/**
* 注解配置bean的做法
* 1.找到含有注解@service的类,通过反射技术实例化对象并且存储待用(map集合)
* 2.对外提供获取实例对象的接口
*/
private static Map<String,Object> map = new HashMap<>(); // 存储对象
static {
//获取包com.lagou.edu下面的所有的Java文件
Reflections reflections = new Reflections("com.lagou.edu");
//找出带有service注解的class
Set<Class<?>> containServiceClass = reflections.getTypesAnnotatedWith(Service.class);
containServiceClass.stream().forEach(beanServiceClass->{
try {
//将class实例化成对象
Object obj = beanServiceClass.newInstance();
//获取该类的注解信息
Service serviceAnnotation = beanServiceClass.getAnnotation(Service.class);
//获取注解值(注解括号的值,别名)
String annotationValue = serviceAnnotation.value();
//如果么有别名,取类名
if(annotationValue==null || "".equals(annotationValue)){
String simpleName = beanServiceClass.getSimpleName();
//如果是接口实现类,即该类是实现接口的,比如TransferServiceImpl实现接口TransferService
Class<?>[] interfaces = beanServiceClass.getInterfaces();
if(interfaces!=null && interfaces.length>0){
simpleName = interfaces[0].getSimpleName();
}
//驼峰命名法,首字母小写
annotationValue = (new StringBuilder()).append(Character.toLowerCase(simpleName.charAt(0))).append(simpleName.substring(1)).toString();
/************将驼峰命名法的类名+该类的实例,以键值对的形式放到map中,备用*********/
System.out.println("开始加载对象名称:"+annotationValue+",值:"+obj);
map.put(annotationValue,obj);
/************将驼峰命名法的类名+该类的实例,以键值对的形式放到map中,备用*********/
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
//通过反射获取map中实例的属性信息:找到带有注解@Autowired
if(!map.isEmpty()){
for(Map.Entry<String,Object> entry:map.entrySet()){
Object obj = entry.getValue();
String key = entry.getKey();
//获取该对象的class信息
Class<?> clazz = obj.getClass();
//通过反射,找到这个类的自己的全部属性(注意:父类的属性这里取不到)
Field[] fields = clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++){
Field field = fields[i];
//判断该属性是否使用了注解@Autowired
if(field.isAnnotationPresent(Autowried.class)){
//设置访问权限
field.setAccessible(true);
//在map容器中找到属性的对象-一定存在,否则报错。注意:标注@Autowird的注解属性,这个类会加@Service或者@Mapper等等注解。
Object value = map.get(field.getName());
//为属性赋值
try {
System.out.println("对象名称:"+key+",开始加载装配对象,名称:"+field.getName()+",值:"+value);
field.set(obj,value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
//通过类名的获取该对象(默认首字母小写)
public static Object getBean(String name){
Object o = map.get(name);
return o;
}
3.改造ProxyFactory,现在通过找到类中或者方法上存在的@Transactional注解情况,本文是注解到类上为例,通过对象,生成它的动态代理对象,来执行转账操作。动态代理在这里其实做了一个增强操作:
(1)转账之前,开启事务(关闭事务的自动提交)
(2)正常业务执行操作
(3)提交事物
(4)异常回滚。异常回滚生效的原因,其实就是一系列数据库的操作,绑定到同一个数据库连接上,才能回滚。
(5)两种动态代理方法,JDK动态代理和cglib动态代理。有接口实现的类,我们优先使用JDK动态代理(毕竟是JDK提供的,jdk8优化了实现,高效),注意,无接口实现的类,不能使用JDK的动态代理。cglib动态代理有无接口实现的类都行。
代码如下:
@Service
public class ProxyFactory {
@Autowried
private TransactionManager transactionManager;
/**
* JDK动态代理
* @param obj
* @return
*/
public Object getJDKProxy(Object obj){
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取Class对象
Class<?> clazz = obj.getClass();
Object result = null;
//如果这个class对象包含@Tansaction注解,加入手动开启事物,手动提交事物的操作
if(clazz.isAnnotationPresent(Transactional.class)){
try {
// 开启事务(关闭事务的自动提交)
transactionManager.beginTransaction();
//执行正常的业务逻辑,返回结果
result = method.invoke(obj,args);
// 提交事务
transactionManager.commit();
}catch (Exception e){
e.printStackTrace();
//异常操作,回滚事务
transactionManager.rollback();
//抛出异常
throw e;
}
}else{
//没有@Tansaction注解,不使用事务,直接执行业务方法
result = method.invoke(obj, args);
}
return result;
}
});
}
/**
* 使用cglib动态代理
* @param obj
* @return
*/
public Object getCglibProxy(Object obj){
return Enhancer.create(obj.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//获取Class对象
Class<?> clazz = obj.getClass();
//如果这个class对象包含@Tansaction注解,加入手动开启事物,手动提交事物的操作
Object result = null;
if(clazz.isAnnotationPresent(Transactional.class)){
try {
// 开启事务(关闭事务的自动提交)
transactionManager.beginTransaction();
//执行正常的业务逻辑,返回结果
result = method.invoke(obj,objects);
// 提交事务
transactionManager.commit();
}catch (Exception e){
e.printStackTrace();
//异常操作,回滚事务
transactionManager.rollback();
//抛出异常
throw e;
}
}else{
//没有@Tansaction注解,不使用事务,直接执行业务方法
result = method.invoke(obj, objects);
}
return result;
}
});
}
//获取动态代理
public Object getProxy(Object obj){
try{
Class<?> clazz = obj.getClass();
Class<?>[] interfaces = clazz.getInterfaces();
Object result;
//有实现接口的类,优先使用jdk提供的动态代理
if(interfaces!=null && interfaces.length>0){
result = getJDKProxy(obj);
}else{
//没有接口的实现类,只能使用cglib动态代理
result = getCglibProxy(obj);
}
return result;
}catch (Exception e){
e.printStackTrace();
throw e;
}
}
}
4.注释掉bean.xml中的配置,这些都没有用了,删掉文件也行。
<?xml version="1.0" encoding="UTF-8" ?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
<!--id标识对象,class是类的全限定类名-->
<!-- <bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">-->
<!-- <property name="ConnectionUtils" ref="connectionUtils"/>-->
<!-- </bean>-->
<!-- <bean id="transferService" class="com.lagou.edu.service.impl.TransferServiceImpl">-->
<!-- <!–set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应的值–>-->
<!-- <property name="AccountDao" ref="accountDao"></property>-->
<!-- </bean>-->
<!--配置新增的三个Bean-->
<!-- <bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>-->
<!--事务管理器-->
<!-- <bean id="transactionManager" class="com.lagou.edu.utils.TransactionManager">-->
<!-- <property name="ConnectionUtils" ref="connectionUtils"/>-->
<!-- </bean>-->
<!--代理对象工厂-->
<!-- <bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory">-->
<!-- <property name="TransactionManager" ref="transactionManager"/>-->
<!-- </bean>-->
</beans>
5.(1)给JdbcAccountDaoImpl实现类,TransferServiceImpl实现类,connectionUtils工具类,TransactionManager事物管理器,ProxyFactory代理工厂类加上@Service注解,给这些的属性加上@Autowired注解,注意,去掉之前的set注入。
(2)给TransferServiceImpl转账实现类,添加@Transactional事务注解。
如下图:
6.改造完成以后,启动服务,我们可以看到效果。
(1)转账前:
(2)转账操作:
(3) 转账后:
(4)转账异常情况,回滚就不上图了,大家都需要可以参考代码:https://gitee.com/sznsky/lagou-transfer