Java 的反射技术和多态特性是框架开发、组件解耦的核心,在这方面,Spring 的 IOC 和 DI 为我们提供了一个极好的学习范例,Spring 的 IOC 使用反射技术创建、管理对象,DI 使用多态技术为组件注入依赖对象。
在没有学习 Spring 之前,简单的解决方案是使用一个 .properties 文件保存程序中使用的接口、实现类类型键值信息,然后在程序中使用一个全局 Properties 对象保存这些信息,并且使用反射技术把这些实现类初始化、提供一个静态的方法获取指定接口的实现类对象,在组件中就可以使用依赖对象的键获取需要的对象。
这样的方案带来的好处就是:当我们需要修改某个组件的实现方式时,比如把之前 JDBC 的 DAO 实现改为 Hibernate 实现,只要把这些新的实现类放到 classpath 下,把 .properties 文件对应接口的实现类类型改成新的 Hibernate 实现类,而不需要修改依赖组件的代码。
1、接口和抽象类
共同点:
(1)二者都不能创建对象;
(2)抽象方法要被实现,不能是静态的,也不能是私有的;
(3)一个类如果没有实现父类或接口的全部抽象方法,那么该类只能为抽象类
区别:
(1)接口可以继承接口,而且可以多继承,但是不能实现;类只能继承一个父类或抽象类,但可以实现多个接口;
(2)抽象类可以有构造方法,接口不能有构造方法;
(3)接口都是抽象方法;抽象类可以没有抽象方法,而且一个类如果有抽象方法,那么这个类就必须是抽象类;
(4)接口只能定义公共静态的常量;抽象类中可以定义普通变量;
(5)接口更适合做子系统、组件、模块的技术、功能规范;抽象类更适合做通用业务逻辑的抽象和封装。比如:DAO、Service 层的核心功能就需要使用接口来规范;而DAO层实现类都具有的一些通用逻辑就可以放在一个抽象类中,然后让具体的DAO实现类来继承这个抽象类。
2、方法的重写和重载
方法的重写 Override 和重载 Overload 都是 Java 多态的不同表现。重写 Override 是父类与子类之间多态性的一种表现,重载 Overload 是一个类中多态性的一种表现。
重写:也叫做覆盖。子类中的方法与父类中的某一方法具有相同的方法名、返回值类型和参数列表,则该方法将覆盖父类方法。有几点需要注意:
(1)子类方法不能抛出比父类方法更多或“更大”的异常;
(2)子类方法的访问权限级别不能比父类方法更严格;
(3)一定要有相同的返回值类型;
(4)参数列表需要相同,如果不同,那么这个方法相当于重载,而不是重写
重载:一个类中定义了多个同名的方法,它们的参数个数或参数类型不同,这种情况就是方法重载。但是和返回值的类型无关。
3、示例代码概述
目录结构如下:
4、DAO接口和默认实现
UserDao接口
1 public interface UserDao { 2 3 public void add(); 4 5 public void del(); 6 7 public void update(); 8 9 public void get(); 10 }
UserDaoImpl 实现类
1 public class UserDaoImpl implements UserDao { 2 3 public void add() { 4 System.out.println("使用的是demo.dao.impl.UserDaoImpl的添加方法"); 5 } 6 7 public void del() { 8 9 System.out.println("使用的是demo.dao.impl.UserDaoImpl的删除方法"); 10 } 11 12 public void update() { 13 14 System.out.println("使用的是demo.dao.impl.UserDaoImpl的更新方法"); 15 } 16 17 public void get() { 18 19 System.out.println("使用的是demo.dao.impl.UserDaoImpl的获取方法"); 20 } 21 }
5、Service接口和默认实现
UserService 接口
1 public interface UserService { 2 3 public void addUser(); 4 5 public void delUser(); 6 7 public void updateUser(); 8 9 public void getUser(); 10 }
UserServiceImpl 实现类,注意在这里需要使用对象工厂注入依赖的 DAO 对象
1 public class UserServiceImpl implements UserService { 2 3 private UserDao userDao; 4 5 public UserServiceImpl() { 6 super(); 7 this.userDao = (UserDao) ObjectFactory.getObject("userDao"); 8 } 9 10 public void addUser() { 11 this.userDao.add(); 12 System.out.println("使用的是demo.service.impl.UserServiceImpl的addUser方法"); 13 } 14 15 public void delUser() { 16 this.userDao.del(); 17 System.out.println("使用的是demo.service.impl.UserServiceImpl的delUser方法"); 18 } 19 20 public void updateUser() { 21 this.userDao.update(); 22 System.out 23 .println("使用的是demo.service.impl.UserServiceImpl的updateUser方法"); 24 } 25 26 public void getUser() { 27 this.userDao.get(); 28 System.out.println("使用的是demo.service.impl.UserServiceImpl的getUser方法"); 29 } 30 }
6、ObjectFactory 类和 objects.properties
首先,看一下 objects.properties 配置文件,主要配置程序使用的接口实现类类型信息
1 userDao=demo.dao.impl.UserDaoImpl 2 userService=demo.service.impl.UserServiceImpl
我们需要写个 ObjectFactory 类读取配置并实例化对象,然后保存到全局“工厂”
1 public final class ObjectFactory { 2 3 /** 4 * 保存接口的键(通常使用接口的简单类名首字母小写)和实现类对象信息<br/> 5 * 6 * 使用者可以调用getObject方法传入配置文件中key获取对应实现类对象<br/> 7 */ 8 private static Map<String, Object> objectMap = new HashMap<String, Object>(); 9 10 /** 11 * 用于加载配置文件 12 */ 13 private static Properties prop = new Properties(); 14 15 static { 16 try { 17 // 读取配置文件 18 prop.load(ObjectFactory.class.getClassLoader().getResourceAsStream("objects.properties")); 19 // 迭代properties,对实现类逐一进行实例化 20 // 然后把实现类对象保存到objectMap中 21 for(Object k : prop.keySet()) { 22 String key = k.toString(); 23 // 获取值,即实现类的权限定名 24 String className = prop.get(key).toString(); 25 // 加载类并实例化 26 Class<?> cls = Class.forName(className); 27 Object obj = cls.newInstance(); 28 // 把实现类对象保存到objectMap 29 objectMap.put(key, obj); 30 } 31 } catch (IOException e) { 32 e.printStackTrace(); 33 throw new RuntimeException("加载全局接口实现类配置文件失败", e); 34 } catch (ClassNotFoundException e) { 35 e.printStackTrace(); 36 throw new RuntimeException("有实现类未找到", e); 37 } catch (InstantiationException e) { 38 e.printStackTrace(); 39 throw new RuntimeException("类实例化出错", e); 40 } catch (IllegalAccessException e) { 41 e.printStackTrace(); 42 throw new RuntimeException("类访问出错", e); 43 } 44 } 45 46 /** 47 * 根据指定接口的键(通常使用接口的简单类名首字母小写)获取对应配置的实现类对象 48 */ 49 public static Object getObject(String key) { 50 return objectMap.get(key); 51 } 52 }
7、Servlet 和 JSP
DecoupleServlet 类,需要使用对象工厂获取 Service 对象
1 public class DecoupleServlet extends HttpServlet { 2 3 private static final long serialVersionUID = 1L; 4 5 public DecoupleServlet() { 6 super(); 7 } 8 9 protected void doGet(HttpServletRequest request, 10 HttpServletResponse response) throws ServletException, IOException { 11 doPost(request, response); 12 } 13 14 protected void doPost(HttpServletRequest request, 15 HttpServletResponse response) throws ServletException, IOException { 16 17 request.setCharacterEncoding("UTF-8"); 18 response.setContentType("text/html; charset=utf-8"); 19 20 UserService userService = (UserService) ObjectFactory 21 .getObject("userService"); 22 23 userService.addUser(); 24 25 request.setAttribute("service", userService.getClass().getName()); 26 27 request.getRequestDispatcher("demo.jsp").forward(request, response); 28 } 29 }
index.jsp 和 demo.jsp 省略。
启动项目后访问 http://localhost:8080/demo/
点击页面链接
从页面和控制台输出,可以看出程序确实使用了默认的 DAO 和 Service
8、修改 DAO 实现类配置
首先,编写一个新的 DAO 实现类 UserHibernateDao
1 public class UserHibernateDao implements UserDao { 2 3 public void add() { 4 System.out.println("使用的是demo.dao.hibernate.UserHibernateDao的添加方法"); 5 } 6 7 public void del() { 8 System.out.println("使用的是demo.dao.hibernate.UserHibernateDao的删除方法"); 9 } 10 11 public void update() { 12 System.out.println("使用的是demo.dao.hibernate.UserHibernateDao的更新方法"); 13 } 14 15 public void get() { 16 System.out.println("使用的是demo.dao.hibernate.UserHibernateDao的获取方法"); 17 } 18 }
修改配置
1 # userDao=demo.dao.impl.UserDaoImpl 2 userDao=demo.dao.hibernate.UserHibernateDao 3 userService=demo.service.impl.UserServiceImpl
重启项目,重新访问
可以看到控制台输出,已经成功地使用了新的 DAO 实现类。