javaWEB简单商城项目(四)

接着上一篇javaWEB简单商城项目(三),这一篇学习基于反射的工厂模式和java依赖注入在项目中的使用

一.java反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
说的通俗点不像以前那样通过new创建对象,现在通过类的限定名即可创建对象.

1.通过反射获取对象

程序通过类的完整限定名创建出了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();
        }
    }

2.通过反射调用类方法

基于反射调用方法,主要通过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)原则,翻译过来就是开闭原则,意思是项目应该对扩展开放,对修改关闭,也就是做到最少的修改而完成所想要的变动.

1.简单工厂模式

在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的时候,就需要在这里面修改其相应的方法

2.工厂方法模式

工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。

首先定义抽象父类接口

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(),太长了

3.基于配置文件的工厂

基于配置文件的意思就是我们把一些参数写到配置文件中,由一个类通过读取配置文件信息,创建我们需要的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依赖注入

为什么叫“依赖注入”:纵观所有的Java应用,它们都是由一些互相协作的对象构成的。我们称这种互相协作的关系为依赖关系。假如A组件调用了B组件的方法,我们可称A组件依赖于B组件。系统创建的实例供调用者调用,也可以看作是系统将创建的实例注入调用者。

1.依赖注入setXXX()方法

前面我们在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方法的话,那么程序因为在配置文件中找不到相应的数据,就会报错

2.使用Annotation优化注入

什么是Annotation?就是在方法前面@符号引出的代码,如下图
javaWEB简单商城项目(四)_第1张图片
因此我们可以创建自己的Annotation:Dao,想要实现的效果如下

  • 当@Dao(“UserDao”)的时候注入UserDao
  • 当@Dao不带参数的时候使用setXXX()注入

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()时候的缺点

javaWEB简单商城项目(四)_第2张图片

你可能感兴趣的:(java,Web,工厂模式)