Spring学习

1. 模拟实现Spring.
为什么要使用Spring呢?先考虑下最常用的访问数据库的框架:
1)POJO层有一个类User
2)Service层有一个类UserService,里面有一个成员变量User u,一个addUser(User u)方法
3)DAO层用来访问数据库(数据库可以在MySQL和Oracle之间任意切换):有一个UserDao接口(里面有一个addUser(User u)方法),该接口下面有两个实现类UserDaoMySQL, UserDaoOracle, 分别实现了addUser方法。
这样,在UserService中调用DAO层来访问数据库的时候,需要在addUser方法中new一个具体实现的DAO. 如:UserDao dao = new UserDaoMySQL();
但当我们想要切换数据库到Oracle的时候,我们需要将这句代码改成UserDao dao = new UserDaoOracle();
这样代码耦合度高,且需要重新编译class.

Spring有效的解决了上述问题:将变化的部分放到XML文件里面,然后对XML文件做解析(通过ClassPathXmlApplicationContext类),这样可以实现:
1)动态为UserDao user实例化对象(通过反射来做到)
2)动态为UserService的成员变量User u赋值(通过反射来做到)

Spring的模拟实现:
1) 写一个beans.xml: 将变化的部分/需要动态实例化/需要依赖注入的部分放入XML中。
<beans>
<!-- userDaoImpl表示UserDaoImplMySQL的instance -->
<bean id="userDaoImpl" class="source.dao.impl.UserDaoImplOracle" />

<!-- userService表示UserService的instance -->
<bean id="userService" class="source.service.UserService" >
<!-- 把userDaoImpl赋值给UserService类的成员变量userDao -->
<property name="userDao" ref="userDaoImpl"/>
</bean>
</beans>
2) ClassPathXmlApplicationContext.java: 用于解析beans.xml
package source.spring;

import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;

/*
* 用于解析spring中的beans.xml:将xml的内容解析后存到名为beans的map中
*/
public class ClassPathXmlApplicationContext implements BeanFactory
{

    private Map<String, Object> beans = new HashMap<String, Object>();

    // IOC Inverse of Control DI Dependency Injection
    public ClassPathXmlApplicationContext () throws Exception
    {
        SAXBuilder sb = new SAXBuilder();
        InputStream file = new FileInputStream("src/source/beans.xml");
        Document doc = sb.build(file); // 构造文档对象
        Element root = doc.getRootElement(); // 获取根元素HD
        List list = root.getChildren("bean");// 取名字为disk的所有元素
        for (int i = 0; i < list.size(); i++) {
            Element element = (Element)list.get(i);
            String id = element.getAttributeValue("id");
            String clazz = element.getAttributeValue("class");
            Object o = Class.forName(clazz).newInstance();
            System.out.println(id);
            System.out.println(clazz);
            beans.put(id, o);

            for (Element propertyElement : (List<Element>)element.getChildren("property"))
            {
                String name = propertyElement.getAttributeValue("name"); // userDAO
                String bean = propertyElement.getAttributeValue("ref"); // u
                Object beanObject = beans.get(bean);// UserDAOImpl instance

                String methodName = "set" + name.substring(0, 1).toUpperCase()
                    + name.substring(1);
                System.out.println("method name = " + methodName);

                Method m = o.getClass().getMethod(
                    methodName,
                    beanObject.getClass().getInterfaces()[0]);
                m.invoke(o, beanObject);
            }

        }

    }

    @Override
    public Object getBean (String id)
    {
        return beans.get(id);
    }

}

3)UserService.java: 把UserDao作为成员变量,并提供set,get方法
public class UserService
{
    UserDAO userDao;
   
    public UserDAO getUserDao ()
    {
        return userDao;
    }

    public void setUserDao (UserDAO userDao)
    {
        this.userDao = userDao;
    }

    public void saveUser(User u) {
        userDao.addUser(u);
    }
}

4) JUnit测试类UserServiceTest.java:
public class UserServiceTest
{

    @Test
    public void test () throws Exception
    {
        BeanFactory factory = new ClassPathXmlApplicationContext(); //解析xml, 通过反射做依赖注入。
        UserService service = (UserService)factory.getBean("userService");
        User u = new User();
        service.saveUser(u);
    }

}

这样,在需要切换数据库的时候,只需要修改beans.xml即可。且动态为UserService.java的成员变量UserDao实例化对象。

2. 什么是依赖注入(DI)/控制反转(IOC)?
1)依赖注入:对于UserService的成员变量userDao, 它是依赖于容器给它“注入”对象,所以叫依赖注入。
2)控制反转:对于UserService的成员变量userDao,原来是由代码做实例化,如UserDao userDao = new UserDaoImplMySQL(); 现在是由容易来控制实例化的过程,所以叫控制反转。

3. Annotation.
1)@Required:This annotation simply indicates that the affected bean property must be populated at configuration time. 即@Required修饰的setter方法必须系统初始化的时候被注入,否则报错。
2) @resource: 默认是byType
如果要指定byName, 可以这样用:
    @Resource(name="userDaoImpl")
    public void setUserDao (UserDAO userDao)
    {
        this.userDao = userDao;
    }

3) @Autowired: 在setter方法上面修饰,会自动注入,默认为byType.

@Autowired 和@Resource 的区别?
1. @Autowired属于Spring的;@Resource为JSR-250标准的注释,属于J2EE的。
2. @Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,例如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用:
@Autowired()
@Qualifier("baseDao")
private BaseDao baseDao;
3. Resource,默认安装名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Resource(name="userDaoImpl")
public void setUserDao (UserDAO userDao)

注:@Resource(name="userDaoImpl") 等价于:
@Autowired()
@Qualifier("userDaoImpl")

4. annotation与xml的优缺点比较:
1) annotation比xml更简单直观。
2) 但annotation与java源代码紧耦合,如果要修改注入规则,则需要修改java代码,不如xml解耦性更好

5. AOP前瞻:为什么说组合比继承更灵活?
如果要在userDaoImplOracle.java 的addUser方法中加入新的逻辑,此时有两种方式:
1)写一个类userDaoImplOracle2.java来继承userDaoImplOracle.java,然后在userDaoImplOracle2.java的addUser方法中Override其父类的addUser方法,但使用继承使得与父类紧耦合:修改父类的话,也需要修改子类,且子类不能继承其他的类。
2)使用组合关系:userDaoImplOracle3.java:
在其中加入成员变量private UserDao userDao = new UserDaoImplOracle();
然后在其addUser方法中调用userDao.addUser方法。

6. 动态代理:AOP的底层实现原理
JDK的动态代理:目的是在某一层的系列的方法前执行..., 在方法后执行...
1)LogInterceptor作为实现了InvocationHandler的接口的类。
public class LogInterceptor implements InvocationHandler
{
    private Object target;
   
    public Object getTarget ()
    {
        return target;
    }

    public void setTarget (Object target)
    {
        this.target = target;
    }

    private void before (Method method)
    {
        System.out.println(method.getName() + " before...");
    }

    @Override
    public Object invoke (Object proxy, Method method, Object[] args) throws Throwable
    {
        before(method);
        method.invoke(target, args);
        after(method);
        return null;
    }
   
    private void after (Method method)
    {
        System.out.println(method.getName() + " after...");
    }

}
2)这样调用:
    @Test
    public void test () throws Exception
    {
        UserDAO userDao = new UserDaoImplOracle();
        LogInterceptor handler = new LogInterceptor();
        handler.setTarget(userDao);
        UserDAO userDaoProxy = (UserDAO)Proxy.newProxyInstance(
            userDao.getClass().getClassLoader(),
            userDao.getClass().getInterfaces(),
            handler);
        userDaoProxy.addUser(new User());
    }

这样,在执行userDaoProxy.addUser方法的时候,首先找到handler,然后执行其invoke方法。

猜你喜欢

转载自doudou-001.iteye.com/blog/2247114