1. IOC

1.1 什么是ioc: Inversion of control(控制反转的思想)

java开发领域对象的创建, 管理的问题。
传统的开发方式: 类A依赖类B, 往往会在类A中new一个B的对象
ioc思想的开发方式: 我们不用自己去new对象, 而是由ioc容器去创建实例化对象并且管理它。我们需要哪个对象, 就去问ioc容器去要。
我们丧失了一个权利(创建, 管理对象)。得到一个福利(不用去考虑对象的创建, 管理等一系列事情)
控制: 对象实例化的权利。
反转: 控制权不在程序本身, 而是交给spring框架(IOC容器)
Spring的核心思想 - 图1

1.2 ioc解决了对象之间的耦合:

Spring的核心思想 - 图2
常规情况下, 调用dao层方法需要new对象(new UserDaoImpl)。 此时需要切换成UserDaoImplNew实现类。所以代码需要改造,耦合度很高。如果使用ioc容器的话, 直接引入接口的依赖即可, 具体实现由ioc容器去完成。

1.3 IOC和DI: DI(dependency injection 依赖注入)

IOC和DI其实做的是同一件事情(对象的实例化以及依赖关系的维护)。
IOC站在对象的角度, 将创建对象的权利给到了容器。
DI: 站在容器的角度,将对象依赖的其他对象注入。如果A对象实例化过程中需要B类型的属性, 那么需要容器将B对象注入给A。

2. AOP:

2.1 什么是AOP

Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程
AOP是OOP的延续, 从OOP说起, OOP三⼤特征: 封装、继承和多态。
Spring的核心思想 - 图3
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,⽐如下⾯的在顶级⽗类 Animal中的多个⽅法中相同位置出现了重复代码,OOP就解决不了横切逻辑代码
Spring的核心思想 - 图4
横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便
AOP提出横向抽取机制,将横切逻辑代码和业务逻辑代码分开在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
Spring的核心思想 - 图5

2.2 为什么叫做⾯向切⾯编程

「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯.

3. 手写AOP和IOC:

在没有任何框架的时, 我们在开发的时候采用的如下方式. 首先是代码之间存在强耦合,不符合面向接口开发的原则。 其次, service层没有事务控制, 会导致数据的错乱。
image.png

3.1 如何解决new对象的问题

我们可以通过反射的机制来实例化对象, 反射需要全限定名, 我们可以通过xml进行配置。并且我们可以通过工厂模式来控制对象的实例化。(项⽬中往往有很多对象需要实例化)
image.png

3.1.2 工厂模式:

https://www.yuque.com/wangyanyang/qz7fwa/qlacwc/edit#8c31ce5b

3.1.3 这里做了什么:

a. 工厂解析xml文件并缓存实例信息

  1. /**
  2. * 工厂类,生产对象(使用反射技术)
  3. */
  4. public class BeanFactory {
  5. /**
  6. * 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
  7. * 任务二:对外提供获取实例对象的接口(根据id获取)
  8. */
  9. private static Map<String,Object> map = new HashMap<>(); // 存储对象
  10. static {
  11. // 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
  12. // 加载xml
  13. InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
  14. // 解析xml
  15. SAXReader saxReader = new SAXReader();
  16. try {
  17. Document document = saxReader.read(resourceAsStream);
  18. Element rootElement = document.getRootElement();
  19. List<Element> beanList = rootElement.selectNodes("//bean");
  20. for (int i = 0; i < beanList.size(); i++) {
  21. Element element = beanList.get(i);
  22. // 处理每个bean元素,获取到该元素的id 和 class 属性
  23. String id = element.attributeValue("id"); // accountDao
  24. String clazz = element.attributeValue("class"); // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
  25. // 通过反射技术实例化对象
  26. Class<?> aClass = Class.forName(clazz);
  27. Object o = aClass.newInstance(); // 实例化之后的对象
  28. // 存储到map中待用
  29. map.put(id,o);
  30. }
  31. // 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值
  32. // 有property子元素的bean就有传值需求
  33. List<Element> propertyList = rootElement.selectNodes("//property");
  34. // 解析property,获取父元素
  35. for (int i = 0; i < propertyList.size(); i++) {
  36. Element element = propertyList.get(i); //<property name="AccountDao" ref="accountDao"></property>
  37. String name = element.attributeValue("name");
  38. String ref = element.attributeValue("ref");
  39. // 找到当前需要被处理依赖关系的bean
  40. Element parent = element.getParent();
  41. // 调用父元素对象的反射功能
  42. String parentId = parent.attributeValue("id");
  43. Object parentObject = map.get(parentId);
  44. // 遍历父对象中的所有方法,找到"set" + name
  45. Method[] methods = parentObject.getClass().getMethods();
  46. for (int j = 0; j < methods.length; j++) {
  47. Method method = methods[j];
  48. if(method.getName().equalsIgnoreCase("set" + name)) { // 该方法就是 setAccountDao(AccountDao accountDao)
  49. method.invoke(parentObject,map.get(ref));
  50. }
  51. }
  52. // 把处理之后的parentObject重新放到map中
  53. map.put(parentId,parentObject);
  54. }
  55. } catch (DocumentException e) {
  56. e.printStackTrace();
  57. } catch (ClassNotFoundException e) {
  58. e.printStackTrace();
  59. } catch (IllegalAccessException e) {
  60. e.printStackTrace();
  61. } catch (InstantiationException e) {
  62. e.printStackTrace();
  63. } catch (InvocationTargetException e) {
  64. e.printStackTrace();
  65. }
  66. }
  67. // 任务二:对外提供获取实例对象的接口(根据id获取)
  68. public static Object getBean(String id) {
  69. return map.get(id);
  70. }
  71. }

xml文件如下:
image.png

b. 实际使用:

image.png

3.2 解决事务的问题:

3.2.1 核心思路:

数据库事务由Connection去控制, 默认事务是自动提交的。对于一个service方法, 会调用多个dao层的方法, 且每一个dao层的方法都会获取一个Connection对象。属于不同的事务。
解决思路: 给当前线程绑定Connection对象, 这样无论service方法调用多少dao层, 其Connection对象是同一个, 事务是同一个。
image.png

public class ConnectionUtils {

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); // 存储当前线程的连接

    /**
     * 从当前线程获取连接
     */
    public Connection getCurrentThreadConn() throws SQLException {
        /**
         * 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到当前线程
          */
        Connection connection = threadLocal.get();
        if(connection == null) {
            // 从连接池拿连接并绑定到线程
            connection = DruidUtils.getInstance().getConnection();
            // 绑定到当前线程
            threadLocal.set(connection);
        }
        return connection;
    }
}

3.2.2 核心方法增强:

默认情况下, 事务是自动提交的, 所以为了解决service事务问题, 需要我们显示的控制事务的开关, 除此之外, service发生异常, 我们的事务要触发回滚。基础代码如下:
image.png
上述代码是属于横切代码, 根据AOP原理, 需要抽取,并增强横切逻辑代码(代理模式)
https://www.yuque.com/wangyanyang/qz7fwa/qlacwc/edit#8c31ce5b

a.将核心方法抽取:

/**
 *
 * 事务管理器类:负责手动事务的开启、提交、回滚
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    // 开启手动事务控制
    public void beginTransaction() throws SQLException {
        connectionUtils.getCurrentThreadConn().setAutoCommit(false);
    }

    // 提交事务
    public void commit() throws SQLException {
        connectionUtils.getCurrentThreadConn().commit();
    }

    // 回滚事务
    public void rollback() throws SQLException {
        connectionUtils.getCurrentThreadConn().rollback();
    }
}
/**
 * <p>
 * <p>
 * 代理对象工厂:生成代理对象的
 */

public class ProxyFactory {


    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.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 {
                        Object result = null;
                        try {
                            // 开启事务(关闭事务的自动提交)
                            transactionManager.beginTransaction();
                            result = method.invoke(obj, args);
                            // 提交事务
                            transactionManager.commit();
                        } catch (Exception e) {
                            e.printStackTrace();
                            // 回滚事务
                            transactionManager.rollback();

                            // 抛出异常便于上层servlet捕获
                            throw e;
                        }
                        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 {
                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;
            }
        });
    }
}

b.在注入的时候, 注入增强的方法

image.png
代码如下:
image.png