接着上一篇javaWEB简单商城项目(三),这一篇学习基于反射的工厂模式和java依赖注入在项目中的使用
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
说的通俗点不像以前那样通过new创建对象,现在通过类的限定名即可创建对象.
程序通过类的完整限定名创建出了User的实例,这就是利用到了反射
public static void main(String[] args) {
String str = "com.model.User";//类的限定名
try {
Class clz = Class.forName(str);//获取类的Class对象
User user = (User) clz.newInstance();//通过Class对象获取User的实例
user.setUsername("Admin");
System.out.println(user.getUsername());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
基于反射调用方法,主要通过Method这个类的invoke()方法,这样做的好处是需要调用的信息,字符串等我们可以写在配置文件中,然后修改就可以直接在配置文件中修改了,后期维护方便太多了
public static void main(String[] args) {
String str = "com.model.User";//类的限定名
String method = "setUsername";
try {
Class clz = Class.forName(str);//获取类的Class对象
User u = (User) clz.newInstance();
/** * 通过getMethod可以获取类方法,第一个参数是方法名,第二个参数是方法参数,可以无限加参数 */
Method method1 = clz.getMethod(method,String.class);
/** * 通过invoke()可以执行这个方法,参数1是执行该方法的对象,参数二是方法的参数 */
method1.invoke(u,"admin");
System.out.println(u.getUsername());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
说工厂模式之前,先说下OCP(open closed Principle)原则,翻译过来就是开闭原则,意思是项目应该对扩展开放,对修改关闭,也就是做到最少的修改而完成所想要的变动.
在com.dao这个包中,每一个实体都有一个对应的DAO,假如实体很多的话,对于DAO管理就需要一个来创建DAO的工厂来管理,如下面例子
import com.dao.AddressDao;
import com.dao.UserDao;
/** * Created by nl101 on 2016/2/26. */
public class DAOFactory {
//获取UserDao
public static UserDao getUserDao(){
return new UserDao();
}
//获取AddressDao
public static AddressDao getAddressDao(){
return new AddressDao();
}
}
唯一的用处就是把Dao统一了起来,用的时候世界DAOFactory.getUserDao()即可
缺点:假如更换数据库,或者更换Dao的时候,就需要在这里面修改其相应的方法
工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。
首先定义抽象父类接口
import com.dao.AddressDao;
import com.dao.UserDao;
/** * Created by nl101 on 2016/2/26. */
public interface AbstractFactory {
public UserDao createUserDao();
public AddressDao createAddressDao();
}
接着定义实现具体方法的子类
import com.dao.AddressDao;
import com.dao.UserDao;
/** * Created by nl101 on 2016/2/26. */
public class MysqlDAOFactory implements AbstractFactory{
/** * 单例设计具体工厂 */
private static AbstractFactory factory = new MysqlDAOFactory ();
private DAOFactory() {
}
public static AbstractFactory getInstance(){
return factory;
}
//获取UserDao
@Override
public UserDao createUserDao(){
return new UserDao();
}
//获取AddressDao
@Override
public AddressDao createAddressDao(){
return new AddressDao();
}
}
同样的还可以有OracleDAOFactory,而他们的方法统一由父类接口来定义,自己只负责实现具体方法.
缺点:修改起来还是麻烦,而且调用需要MysqlDAOFactory.getInstance().createUserDao(),太长了
基于配置文件的意思就是我们把一些参数写到配置文件中,由一个类通过读取配置文件信息,创建我们需要的DAO.
1.首先我们要创建properties文件,里面存储着dao对应的限定名
dao.properties
userdao = com.dao.UserDao
addressdao = com.dao.AddressDao
2.创建抽象工厂 ,工厂里面有一个通用的创建DAO方法
public interface AbstractFactory {
public Object createDao(String name);
}
3.创建peopertiesUtil,用来方便的读取配置文件
import java.io.IOException;
import java.util.Properties;
/** * Created by nl101 on 2016/2/26. */
public class PropertiesUtil {
public static Properties daoProperties = null;
/** * 获取dao配置文件 * @return */
public static Properties getDaoPro(){
//如果已经创建,则直接返回
if (daoProperties!=null){
return daoProperties;
}
daoProperties = new Properties();
try {
daoProperties.load(PropertiesUtil.class.getClassLoader().getResourceAsStream("dao.properties"));//加载配置文件
} catch (IOException e) {
System.out.println("未找到dao配置文件");
e.printStackTrace();
}
return daoProperties;
}
}
4.创建具体工厂,通过传入的name值,利用反射就可以获取到对应的DAO实体类
public class PropertiesFactory implements AbstractFactory{
/** * 首先为工厂实现单例模式 * @return */
private static AbstractFactory factory = new PropertiesFactory();
private PropertiesFactory() {
}
public static AbstractFactory getInstance(){
return factory;
}
/** * 实现父类接口的方法 * @param name 需要创建的dao名字 * @return 创建的dao */
@Override
public Object createDao(String name) {
Properties properties = PropertiesUtil.getDaoPro();
String daoName = properties.getProperty(name);//获取要创建dao对应的限定名
Object obj = null;//承载创建对象的容器
try {
Class clz = Class.forName(daoName);
obj = clz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return obj;
}
}
5.具体工厂优化,对于dao实体我们可以把创建好的存起来,调用的时候先判断是否已经创建,已经创建则返回.所以自然想到了键值对的Map集合.
import com.util.PropertiesUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/** * Created by nl101 on 2016/2/26. */
public class PropertiesFactory implements AbstractFactory{
/** * 首先为工厂实现单例模式 * @return */
private static AbstractFactory factory = new PropertiesFactory();
private PropertiesFactory() {
}
public static AbstractFactory getInstance(){
return factory;
}
private Map<String,Object> maps = new HashMap<>();
/** * 实现父类接口的方法 * @param name 需要创建的dao名字 * @return 创建的dao */
@Override
public Object createDao(String name) {
//判断map中是否已经创建,是则直接返回
if (maps.containsKey(name)){
return maps.get(name);
}
Properties properties = PropertiesUtil.getDaoPro();
String daoName = properties.getProperty(name);//获取要创建dao对应的限定名
Object obj = null;//承载创建对象的容器
try {
Class clz = Class.forName(daoName);//加载class
obj = clz.newInstance();//获取实例
maps.put(name,obj);//存入map中
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return obj;
}
}
调用就可以按照下面方法
UserDao userDao = (UserDao) PropertiesFactory.getInstance().createDao("userdao");
是不是感觉调用还是很麻烦,要写这么长,别急,下面依赖注入就是来解决这个问题的
这样基于配置为工厂模式就比较完美了,如果想换DAO则只要在配置文件中修改下限定名即可,很方便
为什么叫“依赖注入”:纵观所有的Java应用,它们都是由一些互相协作的对象构成的。我们称这种互相协作的关系为依赖关系。假如A组件调用了B组件的方法,我们可称A组件依赖于B组件。系统创建的实例供调用者调用,也可以看作是系统将创建的实例注入调用者。
前面我们在AddressDao中使用了UserDao这个类,我们采用的是UserDao userDao = (UserDao) PropertiesFactory.getInstance().createDao("userdao");
这样的复杂方法,现在通过依赖注入,我们就可以在创建这个类的时候把这个对象初始化好
1.首先我们需要对需要依赖注入的类写上set和get方法,这里我们需要在AddressDao对userDao设置.
所谓的依赖注入就是在初始化类的时候,调用set方法,对userDao进行赋值
/** * 通过依赖注入进行赋值 */
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
2.为了方便,写一个DaoUtil用来存放依赖注入的代码
import com.dao.PropertiesFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/** * Created by nl101 on 2016/2/26. */
public class DaoUtil {
/** * dao依赖注入方法 * @param obj */
public static void daoInject(Object obj){
//获取当前类的不包括继承下来的方法
Method[] methods = obj.getClass().getDeclaredMethods();
try {
//对方法筛选出setXXX方法
for(Method method : methods){
//判断是否以set开始
if (method.getName().startsWith("set")){
//截取set之后的字串和properties相对应
String mm = method.getName().substring(3);
//获取实例
Object o = PropertiesFactory.getInstance().createDao(mm);
//调用set方法进行设置
method.invoke(obj,o);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
3.我们知道所有的Dao都有一个父类,BaseDao,当我们创建某个Dao的时候就会先执行父类的构造方法,所以我们可以在父类的方法中调用依赖注入这个方法
/** * 调用依赖注入方法 */
public BaseDao() {
DaoUtil.daoInject(this);
}
这样做是可以实现创建AddressDao的时候就初始化userDao变量,但是如果AddressDao还有其他set方法的话,那么程序因为在配置文件中找不到相应的数据,就会报错
什么是Annotation?就是在方法前面@符号引出的代码,如下图
因此我们可以创建自己的Annotation:Dao,想要实现的效果如下
1.创建自己的Annotation,从代码可以看到Annotation标识是@interface
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** * 加这个声明,说明当前Annotation在运行的时候执行 */
@Retention(RetentionPolicy.RUNTIME)
public @interface Dao {
String value() default "";
}
其中value()代表他的值,默认是空,当然也可以自定义其他值,比如String abc() default ""
2.使用Annotation,使用很简单,在需要注入的代码上面添加标识就好了
@Dao("UserDao")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
3.修改注入代码,实现上面所说的逻辑
package com.util;
import com.dao.PropertiesFactory;
import com.model.Dao;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/** * Created by nl101 on 2016/2/26. */
public class DaoUtil {
/** * dao依赖注入方法 * @param obj */
public static void daoInject(Object obj){
//获取当前类的不包括继承下来的方法
Method[] methods = obj.getClass().getDeclaredMethods();
try {
//对方法筛选出setXXX方法
for(Method method : methods){
//如果有Dao这个Annotation,则处理
if (method.isAnnotationPresent(Dao.class)){
//获取当前这个Anonotation
Dao dao = method.getDeclaredAnnotation(Dao.class);
//获取其值
String name = dao.value();
//如果值为空,则截取set之后的字符作为值
if (name==null || name.equals("")){
name = method.getName().substring(3);
}
//获取实例
Object o = PropertiesFactory.getInstance().createDao(name);
//调用set方法进行设置
method.invoke(obj,o);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
通过运行发现成功存入数据,这样就解决了setXXX()时候的缺点