不使用框架,我们写按照mvc结构来写代码是这样的:
业务层:service
定义一个业务层接口UserService :
1 public interface UserService { 2 public void addUser(); 3 }
然后根据这个几口写一个业务层实现类UserServiceImpl :
1 public class UserServiceImpl implements UserService { 2 private UserDao userDao = new UserDaoImpl();//接口的实例化问题,自己来new 代码的耦合性很高。 3 @Override 4 public void addUser() { 5 userDao.addUser(); 6 } 7 8 }
持久层UserDao :
定义一个接口持久层接口:
1 public interface UserDao { 2 public void addUser(); 3 }
然后根据这个接口写一个持久层实现类:
1 public class UserDaoImpl implements UserDao { 2 @Override 3 public void addUser() { 4 System.out.println("添加用户!"); 5 } 6 7 }
我们来模拟,层持久层获取数据返回。数据就是“添加用户”这几个字
然后写一个测试类来测试取回的数据:
public class UserDaoTest { private UserService userService = new UserServiceImpl(); //private UserService userService; @Test public void test() { //ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //userService = (UserService) context.getBean("userService"); userService.addUser(); } }
整个流程下来,我们可以发现,传统的方式我们需要在业务层new 持久层的对象。在测试类中,需要new 业务层的对象。也就是说,传统的方式我们获取被依赖对象的方式是使用new关键字,自己在需要的地方自己new。
这种方式使用javaSE里面说的“组合”,通过组合来复用了被依赖对象里面的方法。但是这种方法是很笨重的,笨重表现如下:
1、需要用到1个依赖对象,我们就需要在这个类中主动new1个依赖类的对象,需要用到N个不同的依赖对象,我们就需要主动new N个对象。这样的话,就会使代码变得臃肿,如果业务逻辑很复杂,那这种方式也会增加代码的复杂程度,无论是开发过程中还是给别人看,都不能很容易就梳理出这里的业务。
2、耦合性很强。这一点我以前也一直不明白为什么说耦合性很强,不灵活。现在我终于明白了一点。耦合性强,就意味着可扩展性差。我们可以试想一种情况:
就是项目完成后。发布上线一年了。突然业务的木偶一个小部分需要发生了变化C对象,之前是调用A对象的,现在要改成B对象才合适。这种情况之下:
如果使用传统的new的方法,如何做出修改呢?这时候就需要将C类里面new A对像的地方改为new B对象。然后重新打包,替换原来的包。这项工作就比较负责。也根本谈不上扩展性。
这个时候,如果我们使用另外一种方式---工厂模式,会怎么样呢?肯定会简单很多。
比如:先创建一个工厂类,专门用于创建对象:
1 public class MyFactoryUtils { 2 3 public static <T> T getInstance(Class<T> c) { 4 try { 5 String interfaceName = c.getSimpleName(); 6 String interfaceImplName = ResourceBundle.getBundle("myinterface").getString(interfaceName); 7 return (T) Class.forName(interfaceImplName).newInstance(); 8 } catch (Exception e) { 9 e.printStackTrace(); 10 throw new RuntimeException("加载接口实例失败.." + e); 11 } 12 } 13 }
然后业务层的代码就可以变成这样了:
1 public class UserServiceImpl implements UserService { 2 private UserDao userDao = MyFactoryUtils.getInstance(UserDao .class); 3 @Override 4 public void addUser() { 5 userDao.addUser(); 6 } 7 }
我们通过工厂类,来获取持久层的对象,如:第2行
这个对象是怎么获取的呢?是通过配置文件获取的:
myinterface.properties.
在这个配置文件中,以key--value的方式配置好
UserDao=com.mytest.dao.imple.UserDaoImpl
通过这种方式,我们就大大增强了之前代码的灵活性。如果我们在要更换成别的对象了。我们只要将配置文件中的value换掉就好了。
可是这种方式只能解决耦合性强的问题啊,代码臃肿的问题还是不能解决的。这个时候,spring框架的价值就体现出来了。spring框架不仅较大程度低解决了耦合性强的问题,也解决了代码臃肿的问题。那spring是如何解决耦合性强的问题的呢?
也是通过配置文件。此时spring就相当于一个bean工厂(bean可以简单地理解为那些一类对象)。跟上面的工厂模式一样,只要在配置文件中设置好相应的配置,就可以像上面一样灵活了。
此时使用spring框架的代码是这样的:
1 public class UserDaoTest { 2 private UserService userService; 3 @Test 4 public void test() { 5 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 6 userService = (UserService) context.getBean("userService"); 7 userService.addUser(); 8 } 9 }
我们通过spring提供的工厂对象 context 并根据这个工厂的getBean方法获取需要的对象。配置文件如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 6 <bean id="userService" class="com.myTest.service.impl.UserServiceImpl"></bean> 7 </beans>
这样也就可以实现像工厂模式那样降低了代码的耦合性。可是我们这里明明看到的是,代码要比工厂的那个写得更多啊?其实不是,在这个类里面:ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
这个语句,只要写一次就够了,就可以根据:
userService = (UserService) context.getBean("userService");这种方式获取不同的对象了。要获取不同的对象只要将getBean(id)。里的id换掉就可以了。这样相对工厂方式来说,代码量减少了,灵活性得到了保持。但是这种方式,还是会需要需要很多getBean方法来获取不同的对象,业务一复杂了,也会需要很多个相似的代码来获得不同的对象,灵活性上和代码冗余量可不可以更好一点呢?
这个时候就是DI---依赖注入闪亮登场了。
1 public class UserServiceImpl implements UserService { 2 private UserDao userDao; 3 public void setUserDao(UserDao userDao) { 4 this.userDao = userDao; 5 } 6 @Override 7 public void addUser() { 8 userDao.addUser(); 9 } 10 }
可以看到,我们现在的代码及编程了现在这个样子,代码量明显比之前要少了很多了。配置文件如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 6 <bean id="userDao" class="cn.itcast.dao.impl.UserDaoImpl"></bean> 7 <bean id="userService" class="cn.itcast.service.impl.UserServiceImpl"> 8 <property name="userDao" ref="userDao"></property> 9 </bean> 10 </beans>
我们可以看到在业务层实现类的配置里面,配置了一个属性<
property >,这个正是我们的持久层的对象。这样只要这个业务层一旦初始化,就会同时初始化持久层对象了。
这样,就代码来说,因为使用了工厂,使用了配置文件,相比什么都不使用,直接new的传统写法来说,这样的代码就更加精简,更加具有灵活性了。业务层代码,因为主要代码都集中在了业务上,所以业务也及更好理解了。也因为使用配置文件的方式,想换什么实现方式,在配置文件中将配置文件改了,也就好了,更加地灵活。
到这里,我们可以发现,spring核心的
IOC控制反转,其实就是将创建对象的过程由原来的自己使用new关键字,转变成由spring的工厂去创建。将获得依赖对象的方式由原来的创建式,变成了配置式(在spring的配置文件中配置。)。但是,我个人无法理解的是为什么说是“反转”。我理解的反转是,本来是我为你做的事情,现在变成了你为我做,这叫反转。可是在spring这里,明显就是,本来我为自己做的事情,现在由别人为我做了。所以翻译成了”反转“,会不会对理解上有些误解呢?见仁见智了。
DI,依赖注入。关于这一点,我也是不太能理解(不能理解依赖注入成了spring的特性这一点)。这个依赖注入按照我的理解,就是同样的功能随着IOC而采用了不同的实现方式而已。因为注入,本质是将A对象注入到B对象里面,好让B对象可以使用A对象的方法,而达到复用代码的目的。这个目的使用组合的方式本身就可以实现。或者说为了达到“注入”这种更形象的样子(不考虑spring本身,单论注入),使用组合,完全可以实现。所以为什么依赖注入成了Spring专有的了呢?所以我认为,所谓依赖注入:实际上也就是代码复用的另一种形式,或者干脆说是组合的另外一种形式罢了。(这个问题我的确是有些钻牛角尖了,欢迎多多批评。)