Spring高级-⼿写实现 IoC 和 AOP
一、loC
1)什么是IoC
loc是:IoC Inversion of Control (控制反转/反转控制),它是⼀个技术思想
描述的是:Java开发领域对象的创建,管理的问题
传统开发⽅式:使用new的方式实例化对象,将一个对象(A)引入到另一个对象(B)中;称A依赖于B
IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对
象并且管理它,我们需要使⽤哪个对象,去问IoC容器去即可
2)IoC做到了什么
IoC解决对象之间的耦合问题
之前是new对象,直接将代码写死,将一个实现类写死到了另一个类中,不方便后期的维护和管理
而且每次使用一个对象的时候都要new多个对象,造成资源的浪费
使用loC后,我们将实例化对象交给容器进行管理,程序启动的时候就会将对象创建,只需要用的时候直接取出即可;
3)IoC和DI的区别
IOC和DI描述的是同一件事情,只是站的角度不同
IOC站在对象的角度,将对象实例化及管理的权利交给了容器
DI:Dependancy Injection(依赖注⼊)
DI站在容器的角度,将对象依赖的其他对象送进去
二、AOP
1)什么是AOP
AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程,
AOP:是OOP的延续,
OOP三⼤特征:封装、继承和多态
oop是⼀种垂直继承体系
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,⽐如下⾯的在顶级⽗类
Animal中的多个⽅法中相同位置出现了重复代码,OOP就解决不了
横切逻辑代码
横切逻辑代码(业务代码前后加的代码)存在什么问题:
横切代码重复问题
横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄⽆声息的把横切逻辑代码应⽤到原有的业
务逻辑中,达到和原来⼀样的效果,这个是⽐较难的
2)AOP解决了什么问题
解决代码耦合问题,将业务逻辑代码与横切逻辑代码分离
3)为什么叫做⾯向切⾯编程
「切」:指的是横切逻辑,原有业务逻辑代码不动,只操作横切逻辑代码,所以⾯向横切逻辑
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个
⾯的概念在⾥⾯
三、⼿写实现 IoC 和 AOP思路
接下来根据转账案例来一步步分析并手动实现loc与AOP
存在的问题:
1)问题一:在上述案例实现中,service 层实现类在使⽤ dao 层对象时,直接在 TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对 象,然⽽⼀个 new 关键字却将 TransferServiceImpl 和 dao 层具体的⼀个实现类 JdbcAccountDaoImpl 耦合在了⼀起
如果说技术架构发⽣⼀些变动,dao 层的实现要使⽤其它技术, ⽐如使用Mybatis,思考切换起来的成本?每⼀个 new 的地⽅都需要修改源代码,重新编译,⾯向接⼝开发 的意义将⼤打折扣?
2)问题⼆:service 层代码没有竟然还没有进⾏事务控制,如果转账过程中出现异常,将可能导致 数据库数据错乱,后果可能会很严重,尤其在⾦融业务。
解决思路:
1)针对问题一:
-
使用new关键字实例化对象会将service层与dao层耦合在一起,如果想要在service层使用另一个dao层实现类的话,修改起来会特别的麻烦。
-
除了new关键字实例化对象为,还可以使用反射技术实例化对象(Class.forName(“全限定类名”))-全限定类名:例如com.lagou.edu.JdbcAccountDaoImpl;
-
使用这样的方式,全限定类名也会写死在service中,我们可以将全限定类名配置在xml文件中,从xml中取;
-
生产对象我们可以使用工厂生产对象,工厂+反射技术来生产对象
经过方才分析:我们可以将要实例化的类的全限定类名配置在xml文件中,使用工厂模式,加载工厂类的时候解析配置的xml文件,根据解析出来的全限定类名实例化对象并将实例化的对象存储到工厂类的map集合中;在工厂类中定义一个方法用户获取map中的对象
这样的话我们可以在service层通过工厂类中定义的方法获取需要的dao层对象;
还是不够简洁-需要根据id值获取dao层对象
可以使用构造方法或者set方法将需要的dao层对象传入到service层【这里我们使用set方法】
- service层使用set方法将需要的dao层对象传入到service查
1)首先在xml对象中配置类的依赖关系,可以在bean标签下使用property标签的形式
2)在工厂类实例化所有的对象后,解析xml标签中的property标签,获取peoperty的父标签并调用父标签类的set方法将property标签中配置的dao层对象传入到父类的service对象中
1)针对问题二:
- service层没有事务,需要我们手动添加事务;手动控制JDBC的Connection事务
Connection是自动提交的,不能进行事务控制,需要我们在一个线程中使用同一个连接;所以连接需要和线程绑定(使用单例设计模式:饿汉式或者懒汉式)一个线程中只能存在一个连接(Connection)
定义一个连接工具类,在类中使用单例设计模式将线程与连接绑定
-
在dao层的方法中删除connnection.close()方法,因为关闭连接会将连接放会到线程池中
-
定义一个事务类封装事务,将开启事务、提交事务与回滚事务的代码封装进去
没法方法中都要使用try catch 都要写开启事务、提交事务、回滚事务,相当麻烦;
我们可以将业务逻辑代码与事务控制代码分离开,可以使用代理模式
- 定义代理工程类,使用代理模式将业务逻辑与业务逻辑代码与事务控制代码分离
注意:需要知道他们之间的关系
service层实现类-----依赖于-----dao层实现类
dao层实现类-----依赖于-----Connection工具类
封装的事务类-----依赖于-----Connection工具类
代理工厂类-----依赖于-----封装的事务类
servlet层类-----依赖于-----代理工程类
知道这些可以在beans.xml中修改依赖关系,修改代码可以使用set方法将对象引入到对应的类中
<?xml version="1.0" encoding="UTF-8" ?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
<!--id标识对象,class是类的全限定类名-->
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl">
<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>
四、Spring loC三种模式开发
纯xml模式、xml与注解相结合模式、纯注解模式
开发之前首先要知道:
beans.xml中写了什么:定义了需要实例化对象的类的全限定类名以及类之间依赖关系描述;
BeanFactory-IOC容器:通过反射技术实例化对象并维护对象之间的依赖关系;
启动 IoC 容器的⽅式
1)Java环境下启动IoC容器
- ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件(不推荐使用)
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
2)Web环境下启动IoC容器
- 从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring ioc容器的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
- 从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>