什么是IOC

在传统项目中,类A要使用类B的某个方法f,这时候一般是这么实现:

1
2
3
4
5
6
public class A {
public void test() {
B b = new B();
b.f();
}
}

这时,B的创建是有A来自己完成的,我们可以说A拥有B的创建、管理权限。

传统方式

但是这种情况下,A和B就是强关联的,我们希望A和B是松耦合的状态,显然直接new肯定是不行的。
所以IOC(控制反转)主要就是描述了一种将对象的创建管理权限交给IOC容器管理,自己不负责创建管理思想

IOC

什么是DI

DI:依赖注入,其实它和IOC描述的是同⼀件事情,只不过角度不同。

上面我们从对象的角度描述了什么是IOC,如果我们把角度切换到IOC容器时,就会发现ioc容器主要完成的事情,就是把A需要的B,进行注入,这个过程就叫依赖注入。

自定义实现IOC

基于上面的解释,要实现一个类似SpringIOC的ioc容器,可以分成几个步骤(基于注解的模式):

  1. 扫描指定包下所有类(或者带指定注解的类);
  2. 进行类的实例化,生成对象;
  3. 对象的属性是否需要依赖注入,如果需要则注入(通过注解判断);
  4. 最后将对象存放到容器中,等待使用;
  5. 测试是否成功;

针对上面的步骤,我们进行一步一步的实现。
首先自定义了一个注解,用于指定需要扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.......
/**
* 完成service加载,区分是否是接口实现类
* @author liufujun
*/
@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {

String value() default "";

}

.......
/**
* 自定义的自动注入注解
* 这个注解是完成属性的自动注入,所以type选择filed
* @author liufujun
*/
@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowire {

}

然后声明一个AnnotationApplication类,作为启动时用来加载需要加载类的入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class AnnotationApplication {

// 扫描的包名
private String packageName;

// 扫描生成的bd存放map
private static Map<String, BeanDefinition> bdMap = new HashMap<>();

public AnnotationApplication(String packageName) throws Exception {
this.packageName = packageName;
initBean();
}

/**
* 初始化Bean
*/
public void initBean() throws Exception {
// 根据包名扫描所有类,判断是否有@Service注解,将有这个注解的类都加载
ClassUtil.getClasses(packageName, bdMap);
// 逐个创建
for(String beanId : bdMap.keySet()){
createBean(bdMap.get(beanId));
}
System.out.println("bean加载完成!");
}
}

其中Bean定义使用的是自定义的BeanDefinition,并非是Spring的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class BeanDefinition {

// bean名称
private String beanId;

// bean对应的class
private Class<?> clazz;

// bean实现的接口
private Class<?>[] interfaces;

public String getBeanId() {
return beanId;
}

public void setBeanId(String beanId) {
this.beanId = beanId;
}

public Class<?> getClazz() {
return clazz;
}

public void setClazz(Class<?> clazz) {
this.clazz = clazz;
}

public Class<?>[] getInterfaces() {
return interfaces;
}

public void setInterfaces(Class<?>[] interfaces) {
this.interfaces = interfaces;
}
}

  在其中定义了扫描的包路径packageName,通过构造方法传入,然后就会调用initBean()进行对象初始化加载,里面通过一个工具类ClassUtil将指定包下所有包含自定义的@Service注解的类,进行加载,最终生成一个BeanDefinition,并根据name或者类名的首字母小写作为key进行存储到bdMap
  然后对扫描到的类进行逐个的实例化,调用createBean生成对象实例,实现过程中判断是否是接口,以及注解中是否自定义了beanName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private Object createBean(BeanDefinition bd) throws Exception {
// 判断是否是接口,如果是接口则去加载实现类,实现类也得有@Service,所以跳过
if (bd.getClazz().isInterface()) {
return initInterface(bd.getClazz());
}

// 判断注解是否传值,指定beanName
String beanId = bd.getClazz().getAnnotation(Service.class).value();
if (null != beanId && !"".equals(beanId) && 0 < beanId.length()) {
// 如果不指定,则用类名首字母小写来代替
bd.setBeanId(beanId);
}
return doCreateBean(bd);
}

/**
* 如果是接口,则去实例化实现类
* @param clazz
* @throws Exception
*/
private Object initInterface(Class clazz) throws Exception {
// 逐个创建
for(BeanDefinition bd : bdMap.values()){
Class<?>[] interfaces = bd.getInterfaces();
if (null != interfaces && 0 < interfaces.length) {
for (Class<?> inter : interfaces) {
if (clazz == inter) {
return createBean(bd);
}
}
}
}
return null;
}

  最终所有的bean都会调用doCreateBean方法,进行具体的bean创建过程。
创建之前,先判断缓存中是否有,有的话表示已经创建过了,直接获取就行;
如果没有,则进行实例化等一系列操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Bean对象存放的map--相当于一级缓存
private static Map<String, Object> singletonBeanMap = new ConcurrentHashMap<>();

// 二级缓存(不设置三级缓存,demo中用不到,二级可以解决简单的依赖注入)
private static Map<String, Object> earlySingletonObjects = new HashMap<>();

// 保存bean是否在创建,防止创建多个
private static Map<String, Boolean> beanCreateStatus = new HashMap<>();

private Object doCreateBean(BeanDefinition bd) throws Exception {
String beanId = bd.getBeanId();
// 判断是否已创建(可能是被依赖的关系会提前创建)
Object singletonObj = singletonBeanMap.get(beanId);
// 一级缓存中没有,但是状态是创建中,则使用二级缓存中的对象
if (singletonObj == null && isBeanInCreating(beanId)) {
singletonObj = this.earlySingletonObjects.get(beanId);
}
if (null != singletonObj) {
return singletonObj;
}
// 如果两次都是空,则创建
// 开始创建之前,先把bean的状态更新,防止多次创建
beanCreateStatus.put(beanId, true);
// 通过反射技术实例化对象,调用无参构造
Object obj = bd.getClazz().newInstance();
// 实例化之后放入二级缓存
earlySingletonObjects.put(beanId, obj);
// 进行属性赋值
initBeanWire(obj);
// 判断是否有@Transactional
obj = createTransactionObj(obj);
// 装入一级缓存,删除二级缓存
singletonBeanMap.put(beanId, obj);
earlySingletonObjects.remove(beanId);
beanCreateStatus.remove(beanId);
// 返回对象给调用者
return obj;
}

private boolean isBeanInCreating(String beanId){
Boolean flag = beanCreateStatus.get(beanId);
if (null != flag) {
return flag;
}
return false;
}

  创建的时候,先进性状态标记,防止多个创建同时发生;并且我们使用二级缓存,将实例化完成的对象进行存放,防止循环依赖的情况。
  实例化之后,就是进行属性注入,调用initBeanWire方法,判断是否有自定义的注入注解例如@Autowire,进行逐个的属性注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void initBeanWire(Object o) throws Exception {
Class clazz = o.getClass();
// 获取所有属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//获取注解
Annotation[] annotations = field.getAnnotations();
for (Annotation ann : annotations) {
//判断是否是自定义的Autowired注解
if (ann instanceof Autowire){
// 获取参数类型
Class filedType = (Class) field.getGenericType();
String beanId = ClassUtil.getBeanIdByClass(filedType);
// 创建依赖的参数对象
Object bean = createBean(bdMap.get(beanId)); //此处里面会进行缓存判断,这里就不再写缓存相关
// 防止private属性
field.setAccessible(true);
field.set(o, bean);
System.out.println("属性注入成功");
}
}
}
}

  最终会重新调用createBean去生成依赖的对象,如果对象存在则直接缓存获取,没有的时候,就会去创建。

循环依赖

  循环依赖指的是:A里依赖B,B里同时也依赖A;当创建A的时候,属性注入B,发现B还不存在,则会去创建B,B创建的时候发现依赖A,但是A在创建中也没有创建完成,最终产生错误,这就是循环依赖。

  上面我们自定义的IOC是怎么解决这个问题呢?
  这里我们是进行了一个简单的解决,就是在A实例化(bd.getClazz().newInstance())完成后,将结果放入一个二级缓存private static Map<String, Object> earlySingletonObjects = new HashMap<>();,再去执行属性B的注入,然后当实例化B的时候,发现依赖A,这时直接从二级缓存中获取,打破循环依赖,完成B的创建,最终A的Bean也完成创建。

  为什么要用二级缓存呢?
  首先是将最终的对象与中间对象进行区别,同时因为下面我们要进行AOP的一些操作,会进行代理对象的生成,所以使用一个简单的二级缓存。

实际上,Spring解决循环依赖借助的是三级缓存,具体见Spring相关笔记中的循环依赖问题。

什么是AOP

  了解AOP之前,首先我们需要知道常用的OOP,也就是面向对象编程。
  在OOP中,一切都是对象,它的特性就是封装、继承、多态,所以会进行一些父类的抽取,最终会形成一个垂直的继承体系。

OOP
  OOP编程思想可以解决⼤多数的代码重复问题,但是还是有一些情况解决不了。比如判断方法执行时间、在方法执行前后进行日志记录等,这些操作是与业务代码无关的代码,而且多个方法都出现这样的情况,通过OOP的思想已经无法向上抽取,只能在每个方法中进行重复编写。

  总的来说,这些代码存在以下问题:

  1. 代码重复;
  2. 与业务无关,但是和业务逻辑代码混合在一起,维护不方便;

  这时就产生了AOP的思想,即面向切面编程。
  将这些与业务无关的逻辑(称为横切逻辑)进行拆分提出,形成单独的处理类,最终在具体方法执行时,自动加在执行的前或后,完成切面逻辑与业务逻辑的融合,达到不改变业务类,却可以随时更换切面逻辑的效果。

自定义实现AOP

这里我们通过实现事务管理方式,实现自定义的AOP。
实现步骤:
1)自定义一个事务注解@Transactional
2)把注解声明在类或方法上;
3)创建Bean的时候,进行事务判断,最终生成代理类;

第一步和第二步比较简单,代码就不再展示,当完成方法上的注解添加之后,再去修改AnnotationApplication的创建Bean。
上面的代码中doCreateBean里面调用的createTransactionObj(obj);就是去声明事务注解的加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private Object createTransactionObj(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
boolean proxyFlag = false;
// 判断类上是否有事务的注解
Transactional clazzAnn = clazz.getAnnotation(Transactional.class);
List<Method> methodList = new ArrayList<>();
if (null != clazzAnn) {
proxyFlag = true;
} else {
// 类上没有,则看看方法上有没有
Method[] methods = clazz.getMethods();
for (Method method : methods) {
Transactional methodAnn = method.getAnnotation(Transactional.class);
if (null != methodAnn) {
methodList.add(method);
proxyFlag = true;
}
}
}
if (!proxyFlag) {
return obj;
} else {
String beanId = ClassUtil.getBeanIdByClass(ProxyFactory.class);
// 创建依赖的参数对象
ProxyFactory proxyFactory = (ProxyFactory) createBean(bdMap.get(beanId));
return proxyFactory.getProxyObj(obj, methodList);
}
}

最终将使用了自定义的@Transactional注解的类或者方法,通过代理工厂ProxyFactory生产一个代理对象;

1
2
3
4
5
6
7
public Object getProxyObj(Object obj, List<Method> methodList) {
Class<?>[] interfaces = obj.getClass().getInterfaces();
if (null == interfaces || 0 == interfaces.length) {
return getCglibProxy(obj, methodList);
}
return getJdkProxy(obj, methodList);
}

最终根据是否实现接口,来判断使用哪种代理方式。而在其中的invoke方法中,完成事务的处理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* Jdk动态代理
* @param obj 委托对象
* @return 代理对象
*/
public Object getJdkProxy(Object obj, List<Method> methodList) {
// 获取代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
boolean proxyFlag = false;
// 如果是代理指定方法则进行方法判断
if (null != methodList && 0 < methodList.size()) {
for (Method proxyMethod : methodList) {
if (method.getName().equals(proxyMethod.getName())) {
proxyFlag = true;
}
}
} else {
// 否则就是代理所有的方法
proxyFlag = true;
}
try{
// 开启事务(关闭事务的自动提交)
if (proxyFlag) {
System.out.println("使用JDK动态代理进行事务控制");
transactionManager.beginTransaction();
}
result = method.invoke(obj, args);
if (proxyFlag) {
// 提交事务
transactionManager.commit();
}
}catch (Exception e) {
e.printStackTrace();
if (proxyFlag) {
// 回滚事务
transactionManager.rollback();
}
// 抛出异常便于上层servlet捕获
// throw e;
}
return result;
}
});

}

  最终将返回的代理对象存入缓存中,当使用到相应的方法时,就会使用代理对象先进行代理逻辑执行,完成事务控制的输出,然后再执行业务逻辑代码。

  到此,自定义的AOP也就完成了。 其中,我们默认了代理逻辑等,后续可以将此部分改成从配置中获取,进一步完善。

自测

首先需要增加用户相关dao层、service层,此处不展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

public class ITest {

@Test
public void test() throws Exception {
AnnotationApplication annApp = new AnnotationApplication("com.liufujun.edu");
UserServiceImpl userService = (UserService) annApp.getBean(UserServiceImpl.class);
userService.create("22222222","33333333", 100);
System.out.println("创建完成!");
}

@Test
public void testException() throws Exception {
AnnotationApplication annApp = new AnnotationApplication("com.liufujun.edu");
UserService userService = (UserService) annApp.getBean(UserServiceImpl.class);
userService.createWithException("22222222","33333333", 100);
System.out.println("创建执行完成!");
}

@Test
public void testProxy() throws Exception {
AnnotationApplication annApp = new AnnotationApplication("com.liufujun.edu");
UserProxyService userService = (UserProxyService) annApp.getBean(UserProxyService.class);
userService.createWithException("22222222","33333333", 100);
System.out.println("创建完成!");
}

}

最终可以正常获取到指定的对象,正确的执行对应的方法,并且输出事务aop逻辑日志,验证成功!