文章内容输出来源:拉勾教育Java高薪训练营
文章目录
相关文章
问题
上两篇中实现了IOC容器管理Bean和事务管理的AOP实现。但使用上还有些繁琐。
- 问题一
如下面在单元测试类中的获取代理对象实现事务的代码:
private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
private TransferService transferService = (TransferService)proxyFactory.getJdkProxy(BeanFactory.getBean("transferService"));
它需要显式的去代理工厂获取对象,再做一个强制的类型转换。当业务比较大的时候,就比较繁琐,而且效率低下了。
- 问题二
项目的业务比较大,那创建类就会比较多,每个类都要在beans.xml
中声明一个节点,那长期以来这个配置文件就越来越长,更不好维护。
问题思路
在Spring中,它可以通过@Service,@Repository,@Component来声明Bean,通过@Transactional来声明事务,通过@Autowired来进行依赖注入
接下来跟着Spring的这些思路,手写一下注解实现。
注解实现
1. 创建注解类
- Service/Repository/Component
- 都表示需要IoC容器管理的类,不同的是Service表示业务类,Repository表示数据访问类,Component表示其他组件类
- 可在类上进行声明
- 拥有属性
value
, 如果要自定义bean名称,则可以为此属性赋值
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Repository {
String value() default "";
}
- Transactional
- 可在类或者方法上进行声明
- 没有属性
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Transactional {}
- Autowired
- 表示需要依赖注入
- 在类的属性进行声明,注入时需要属性实现setter方法
- 没有属性
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {}
2. 创建Bean信息类BeanDefinition
public class BeanDefinition {
//Bean对象
private Object object;
//是否配置了事务.1.类配置了事务注解.2.类没有配置事务注解,但是方法上配置了事务注解
private Boolean hasTransation;
//是否拥有接口。存在接口,使用JDK动态代理。不存在接口,使用CGLIB动态代理
private Boolean hasInterface;
}
3. 在配置文件中配置上需要进行扫描放到IOC容器中管理的类所在的包名package
<beans>
<!--扫描包下所有注解的类型-->
<component-scan base-package="com.yyh.service,com.yyh.dao,com.yyh.utils"/>
</beans>
4. 在BeanFactory
的初始化加载Bean方法中增加注解解析处理
1) 解析获取所有的包名
2) 扫描包下的带注解的类
3)获取Bean名称。有返回beanName,则表示有相应的注解,需要去进行实例化操作
- 判断注解上是否有自定义名称,如果有则使用自定义名称
- 如果没有,则判断是否实现了单一接口,如果有则使用接口名称
- 如果没有,则使用类名
- 最后对Bean名称进行处理,首字母小写
4)创建Bean,并放入单例池
- 类标记了事务注解、或者方法标记了事务注解,则标记相应的Bean为配置了事务
- 类实现了接口,则标记相应的Bean为配置了接口
5)获取标识了注解Autowired的属性数据
6)对属性进行注入接口
private static void loadBeansByAnnoation(List<Element> componentScanList) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
//1. 解析获取所有的包名
Set<String> basePackages = getBasePackages(componentScanList);
if(null == basePackages) {
return;
}
Map<String, List<String>> fieldMap = new HashMap<>();
for (String basePackage : basePackages) {
//2. 扫描包下的带注解的类
Set<String> classNames = PackageUtils.scanClassNames(basePackage);
for (String className : classNames) {
Class<?> aClass = Class.forName(className);
//3. 获取Bean名称。有返回beanName,则表示有相应的注解,需要去进行实例化操作
String beanName = getBeanName(aClass);
if(null != beanName) {
//4. 创建Bean,并放入单例池
createBean(beanName, aClass);
//5. 获取标识了注解Autowired的属性数据
fieldMap.put(beanName, getAutoWiredFields(aClass));
}
}
}
//6. 对属性进行注入
injectFields(fieldMap);
}
/**
* 创建Bean实体
* @param beanName
* @param aClass
* @throws IllegalAccessException
* @throws InstantiationException
*/
private static void createBean(String beanName, Class<?> aClass) throws IllegalAccessException, InstantiationException {
Object o = aClass.newInstance();
//检查是否拥有事务
boolean hasTransaction = checkHasTransaction(aClass);
//检查 量澡拥有接口
boolean hasInterface = null != aClass.getInterfaces() && aClass.getInterfaces().length > 0;
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setObject(o);
beanDefinition.setHasTransation(hasTransaction);
beanDefinition.setHasInterface(hasInterface);
beanMap.put(beanName, beanDefinition);
}
/**
* 为属性注入
* @param fieldMap
*/
private static void injectFields(Map<String, List<String>> fieldMap) {
fieldMap.forEach((beanName, fields) -> {
Object o = beanMap.get(beanName).getObject();
Method[] methods = o.getClass().getMethods();
for (String field : fields) {
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if(method.getName().equalsIgnoreCase("set" + field)) {
try {
method.invoke(o, getBean(field));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
});
}
/**
* 获取类中标记了注解的属性
* @param aClass
* @return
*/
private static List<String> getAutoWiredFields(Class<?> aClass) {
//获取到标记了Autowired的注解属性
List<String> fieldNames = new ArrayList<>();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Autowired autoWiredAnno = declaredField.getAnnotation(Autowired.class);
if(null != autoWiredAnno) {
String fieldName = declaredField.getName();
fieldNames.add(fieldName);
}
}
return fieldNames;
}
/**
* 解析获取所有的包名
* @param componentScanList
* @return
*/
private static Set<String> getBasePackages(List<Element> componentScanList) {
if(null == componentScanList || componentScanList.size() == 0) {
return null;
}
Set<String> basePackages = new HashSet<>();
for (int i = 0; i < componentScanList.size(); i++) {
Element element = componentScanList.get(i);
String basePackageAttr = element.attributeValue("base-package");
if(null != basePackageAttr && basePackageAttr.length() > 0) {
for (String p : basePackageAttr.split(",")) {
basePackages.add(p);
}
}
}
return basePackages;
}
/**
* 检查是否有事务配置
* @param aClass
* @return
*/
private static boolean checkHasTransaction(Class<?> aClass) {
//判断方法上是否标识了注解
Method[] methods = aClass.getMethods();
if(null != methods) {
for (Method method : methods) {
if(null != method.getAnnotation(Transactional.class)) {
return true;
}
}
}
//判断类上是否标识了注解
return null != aClass.getAnnotation(Transactional.class);
}
5. 代理工厂ProxyFactory
增加CGLIB动态代理生成对象方法
- 引入依赖
<!--引入cglib依赖包-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
- 增加的代理方法
/**
* 使用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 {
Object result = null;
try{
// 开启事务(关闭事务的自动提交)
transactionManager.beginTransaction();
result = method.invoke(obj,objects);
// 提交事务
transactionManager.commit();
}catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 抛出异常便于上层servlet捕获
throw e;
}
return result;
}
});
}
6. 创建Bean工厂单例工具类BeanFactoryUtils
1)调用Bean工厂的初始化加载Bean方法
2)提供getBean方法(支持泛型)
- 从单例池中获取Bean信息
- 如果Bean是配置了事务,则调用动态代理工厂进行处理
- 根据是否配置了接口,使用JDK动态代理或者Cglib动态代理
public <T> T getBean(String beanName, Class<T> type) {
BeanDefinition beanDefinition = BeanFactory.getBeanDefinition(beanName);
//判断是否配置为事务
if(beanDefinition.getHasTransation()) {
//根据是否存在接口使用不同的代理
Object object = beanDefinition.getHasInterface() ?
proxyFactory.getJdkProxy(beanDefinition.getObject()) : proxyFactory.getCglibProxy(beanDefinition.getObject());
return type.cast(object);
}else {
return type.cast(beanDefinition.getObject());
}
}
7. 在相关的业务类、工具类上声明下注解或者依赖注入的注解
- 修改DAO类
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private ConnectionUtils connectionUtils;
- 修改业务类,自定义bean名称为transferBusiness。并在transfer业务方法上加上事务注解
@Service("transferBusiness")
public class TransferServiceImpl implements TransferService {
@Autowired
private AccountDao accountDao;
@Override
@Transactional
public void transfer(String fromCardNumber, String toCardNumber, int money) throws SQLException {
AccountEntity fromAccount = accountDao.selectOneByCardNumber(fromCardNumber);
AccountEntity toAccount = accountDao.selectOneByCardNumber(toCardNumber);
accountDao.updateMoneyByCardNumber(fromCardNumber, fromAccount.getMoney() - money);
//抛出一个异常
int a = 123 / 0;
accountDao.updateMoneyByCardNumber(toCardNumber, toAccount.getMoney() + money);
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
- 修改数据库连接工具类
@Component
public class ConnectionUtils {
- 修改代理工厂类
@Component
public class ProxyFactory {
@Autowired
private TransactionManager transactionManager;
}
- 修改事务管理类
@Component
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
- 修改beans.xml,去掉多余的bean节点,保留配置注解的扫描包
<beans>
<component-scan base-package="com.yyh.service,com.yyh.dao,com.yyh.utils"/>
</beans>
8. 单元测试
public class TransferTest {
@Test
public void testTransfer() throws SQLException {
//根据自定义的名称,使用BeanFactory工具类去拿到业务对象
TransferService transferService = BeanFactoryUtils.getInstance().getBean("transferBusiness", TransferService.class);
//执行转账
//为测试事务是否生效,可以在这个方法内部的两个更新方法中间添加上抛出异常的方法
transferService.transfer("6029621011000", "6029621011001", 100);
}
}