简单的说,Spring是一个轻量级的Ioc和AOP 容器 框架。
A)轻量级
无论从大小还是系统开支来说,Spring都算是轻量级的。整个Spring框架也就是1M多一点的Jar包。Spring的处理开支也非常小。它是非侵入式的:基于Spring开发的系统的对象一般不依赖于Spring的类。
B)反向控制(Ioc)
使用Ioc,对象是被动接受依赖类,而不是主动去寻找。这点和JNDI恰好相反。也就是在容器实例化对象的时候主动将它的依赖注入给它。所以说Spring是通过Ioc(控制反转)来实现依赖注入的。
Ioc意味着关于对象如何得到它的协作对象的责任反转了!
C)面向切面(AOP)
通过AOP,将我们的业务逻辑从系统服务(事务管理,Log日志)中分离出来,将我们的关注点只放在业务逻辑部分。在需要系统服务的地点,时间再将系统服务注入进来。
D)容器
Spring是一个容器,是因为它包含并且管理系统对象的生命周期和配置。可以通过配置来设定你的Bean是单一实例(singleton)还是每次请求产生一个实例(prototype).
E)框架
使用简单的组件配置组合成一个复杂的系统。也就是说,通过XML文件配置,要啥有啥!
3)Spring的7个模块
Spring由7个模块组成,注意:这7个模块根据系统的需求进行取舍。
AOP模块,O/R映射模块,JDBC和DAO模块,Web Context和Utility模块,Application Context模块,MVC模块,核心容器和支持工具。
A)核心容器
Spring核心容器为Spring框架提供了基础功能。在后面我们会讲到BeanFactory类,它是Spring框架系统的核心。BeanFactory采用工厂模式来实现Ioc.
B)Application Context模块
如果说BeanFactory使Spring成为容器的话,那么Application Context模块就是让Spring成为框架的原因。它在BeanFactory基础上进行了扩展,添加了对I18N,系统生命周期事件以及验证的支持。另外,还提供了很多企业级服务,如电子邮件服务,JNDI访问,EJB集成,远程调用以及定时服务。
C)Spring的AOP模块
是Spring应用系统开发切面的基础。
D)JDBC抽象及DAO模块
我记得我以前做的项目中,涉及到数据库操作的时候,总是在程序中写一大堆和业务不相干的代码,什么获得连接,创建语句,处理结果,关闭连接。杂乱不堪!!!
现在Spring的JDBC和DAO模块把这些样板代码抽象出来,让我们的数据库代码变得简单明了。另外,这个模块还创建了一个数据库异常层。
E)O/R映射集成模块
Spring并没有实现自己的ORM解决方案,它为许多流行的ORM框架提供了支持。
F)Spring的Web模块
它是建立在Application Context基础之上,提供了适合Web系统的上下文。该模块还支持多项其他面向Web的任务。还包括对Struts的集成支持。
G)Spring MVC框架
虽然Spring可以和其他MVC框架(struts,Web Work)集成,但它也提供了全功能的MVC框架。
上篇博客简单介绍了Spring的一些优点,现在来说一下Spring这些优点实现的基础:控制反转和依赖注入。
控制反转
IoC(Inversion of Control),我们可以把它理解为面向对象编程的一个名词概念。直白的说,就是:本来是你该控制的事,现在交由其他人控制,即权利或责任的反转。在编程中,常见的实例是,A对象需要依赖B对象(在A中new一个B),本该是A控制这个过程,现在交由系统处理,这种做法的优势是A与B的耦合性降低;如果这种依赖关系可以控制,也提高了程序的灵活性。
依赖注入
DI(Dependency Injection),控制反转是一个概念,如何什么手段实现?依赖注入是一个靠谱的方案,依赖注入的主要实现靠的是反射和面向接口编程,常见的如:
依赖注入,就是把这个实例化的过程分为两步:
- 第一步:UserDao userDao;
- 第二步:userDao=new userDao4Mysql();
XML
将使用到的userDao注入到UserManagerImpl中。
构造函数方式
Set方式
如上所见,耦合性降低同时灵活性提升:
- 耦合性降低体现在UserManagerImpl不必再直接关联UserDao4MysqlImpl。
- 灵活体现在userDao4Mysql在配置文件中,可按需要修改,如替换为UserDao4OracleImpl。
以上说的是优点,那么缺点呢?
- 手写XML易产生拼写错误。
- 反射本身生成对象会有一些效率问题(一倍多于直接new)。
以上就是Spring的核心控制反转和依赖注入,下篇博客会说到Spring与Struts1的集成。
上篇博客中说到了Spring的核心实现,这篇博客就是根据这些来说一下Spring对Struts1的集成。
Struts1的问题
来看一下Struts1的实现流程图:
在这没有Spring介入时,常见的Struts1的Action代码如下:
和上篇说到的一样,这种通过new的方式,写的很“死”,Spring如何解决这个问题?
第一种方案
使用查找方式在Action中获取UserManager:
代码为:
这种方法虽然解决了new的问题,也使用了查找的方式,但是很明显,Spring配置文件的读取与逻辑本身没有太大关联,需要分离出来。
再优化
将factory的生成放到容器启动时,web.xml配置文件:
注意contextConfigLocation不能更改,获取所有Spring的配置文件,然后通过listener的方式,在容器启动时,即加载Spring配置文件,按照配置进行对象的依赖注入。
然后通过以下代码得到factory。
但是仍然存在问题,无论如何都不能避免BeanFactory factory……,因为是主动查找,而非注入,即Struts仍然依赖Spring的API,如何解决?
第二种方案
以上问题出现的原因是Action并没有纳入Ioc容器,无法对其进行注入,所以想解决以上问题,务必要将Action纳入Spring管理中,新方案如下:
为此,需要将struts的配置文件修改为:
同时在Spring配置文件中添加:
此时流程变为:
- DelegatingActionProxy接受ActionServlet转发过来的请求
- DelegatingActionProxy将请求转发给Spring下同名的Bean,即真正的Action
- 接下来的流程与Struts1处理相同
说一下DelegatingActionProxy,这个类为代理类,它的作用是将请求转发给Spring下管理的Action,此时在LoginAction中,即可完成UserManagerImpl的注入。
总结
Spring与Struts的集成,主要体现在代理类上面,通过代理类,将真正的Action纳入到IoC容器的管理,进而进行对象注入。
AOP
概念:
AOP是Aspect Oriented Programming的缩写,意思是面向切面编程
功能:
日志记录,性能统计,安全控制,事务处理,异常处理等
原理:
AOP通过反射机制实现动态代理,具体看下面举例吧。
举例:
在业务方法执行前后加日志记录
业务类接口IHello.java
- package demo.aop;
- public interface IHello {
- /**
- * 业务处理A
- *
- * @param name
- */
- void sayHello(String name);
- /**
- * 业务处理B
- *
- * @param name
- */
- void sayGoodBye(String name);
- }
IHello.java的一个实现类
- package demo.aop;
- public class Hello implements IHello{
- public void sayHello(String name) {
- System.out.println("hello " + name);
- }
- public void sayGoodBye(String name) {
- System.out.println(name + " goodbye!");
- }
- }
日志记录相关的类:Logger类和Level枚举
- package demo.aop;
- public enum Level {
- DEBUG, INFO
- }
- package demo.aop;
- import java.util.Date;
- public class Logger {
- public static void logging(Level level, String context) {
- if (Level.DEBUG.equals(level)) {
- System.out.println("DEBUG\t" + new Date().toString() + "\t"
- + context);
- } else if (Level.INFO.equals(level)) {
- System.out.println("INFO\t" + new Date().toString() + "\t"
- + context);
- }
- }
- }
下面我们为这个sayHello业务方法加上日志记录的业务,我们在不改变原代码的情况下实现该功能
思路1:写一个类实现IHello接口,并依赖Hello这个类
- package demo.aop;
- /**
- * Hello代理类
- *
- * @author ydc
- * @date 6:55:42 PM Jul 31, 2013
- */
- public class ProxyHello implements IHello {
- private IHello hello;
- public ProxyHello(IHello hello) {
- this.hello = hello;
- }
- public void sayHello(String name) {
- Logger.logging(Level.DEBUG, "sayHello method start");
- hello.sayHello(name);
- Logger.logging(Level.INFO, "sayHello method end");
- }
- public void sayGoodBye(String name) {
- hello.sayGoodBye(name);
- }
- }
测试一下
- package demo.aop;
- public class AopTest {
- public static void main(String[] args) {
- new AopTest().test1();
- }
- public void test1() {
- // 无日志记录功能
- IHello hello1 = new Hello();
- // 有日志记录功能
- IHello hello2 = new ProxyHello(new Hello());
- hello1.sayHello("wallet white");
- System.out.println("------------------------------");
- hello2.sayHello("wallet white");
- }
- }
结果输出:
- hello wallet white
- ------------------------------
- DEBUG Wed Jul 31 21:46:38 CST 2013 sayHello method start
- hello wallet white
- INFO Wed Jul 31 21:46:38 CST 2013 sayHello method end
缺点:如果像Hello这样的类很多,那么我们就要写很多个ProxyHello这样的类
思路2:在jdk1.3以后,jdk提供了一个API java.lang.reflect.InvocationHandler的类,这个类可以让我们在JVM调用某个类的方法时动态的为这些方法做些其他事
写一个代理类实现DynaProxyHello实现InvocationHandler接口
- package demo.aop;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- /**
- * Hello动态代理类 代理对象与被代理对像解藕
- *
- * @author ydc
- * @date 6:56:27 PM Jul 31, 2013
- */
- public class DynaProxyHello implements InvocationHandler {
- /**
- * 要处理的对象
- */
- private Object delegate;
- /**
- * 动态生成方法被处理过后的对象(写法固定)
- *
- * @param delegate
- * @return Object
- */
- public Object bind(Object delegate) {
- this.delegate = delegate;
- return Proxy.newProxyInstance(
- this.delegate.getClass().getClassLoader(), this.delegate
- .getClass().getInterfaces(), this);
- }
- /**
- * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的
- */
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- Object result = null;
- try {
- // 执行原来的方法之前记录日志
- Logger.logging(Level.DEBUG, method.getName() + " method start");
- // JVM通过这条语句执行原来的方法(反射机制)
- result = method.invoke(this.delegate, args);
- // 执行原来的方法之后记录日志
- Logger.logging(Level.INFO, method.getName() + " method end");
- } catch (Exception e) {
- e.printStackTrace();
- }
- // 返回方法返回值给调用者
- return result;
- }
- }
测试一下
- package demo.aop;
- public class AopTest {
- public static void main(String[] args) {
- new AopTest().test2();
- }
- public void test2() {
- IHello hello = (IHello) new DynaProxyHello().bind(new Hello());
- hello.sayHello("wallet white");
- System.out.println("------------------");
- hello.sayGoodBye("wallet white");
- }
- }
结果输出:
- DEBUG Wed Jul 31 21:57:49 CST 2013 sayHello method start
- hello wallet white
- INFO Wed Jul 31 21:57:49 CST 2013 sayHello method end
- ------------------
- DEBUG Wed Jul 31 21:57:49 CST 2013 sayGoodBye method start
- wallet white goodbye!
- INFO Wed Jul 31 21:57:49 CST 2013 sayGoodBye method end
由此可知,采用面向接口编程,对于任何对象的方法执行之前要加上记录日志的操作都是可以的。
代理对象(DynaProxyHello)自动去代理执行被代理对象(Hello)中的每一个方法,一个InvocationHandler接口就把我们的代理对象和被代理对象解藕了。
问题:要是为不同的业务方法前加的日志记录不同,就需要写多个DynaProxyHello类
加强版:把DynaProxyHello对象和日志操作对象(Logger)解藕
我们要在被代理对象的方法前面或者后面加上日志操作代码 ,那么我们可以抽象一个接口,该接口就只有两个方法
- package demo.aop;
- import java.lang.reflect.Method;
- public interface IOperation {
- /**
- * 方法执行之前的操作
- *
- * @param method
- */
- void start(Method method);
- /**
- * 方法执行之后的操作
- *
- * @param method
- */
- void end(Method method);
- }
写一个实现上面接口的类,把他作为真正的操作者,
- package demo.aop;
- import java.lang.reflect.Method;
- /**
- * 该类将代理者与操作者解藕
- *
- * @author ydc
- * @date 7:22:24 PM Jul 31, 2013
- */
- public class LoggerOperation implements IOperation {
- public void end(Method method) {
- Logger.logging(Level.DEBUG, method.getName() + "method end");
- }
- public void start(Method method) {
- Logger.logging(Level.INFO, method.getName() + "method start");
- }
- }
新建一个代理对象DynaProxyHello2
- package demo.aop;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- /**
- * Hello动态代理类 代理对象与被代理对像解藕
- *
- * @author ydc
- * @date 6:56:27 PM Jul 31, 2013
- */
- public class DynaProxyHello2 implements InvocationHandler {
- /**
- * 操作者
- */
- private Object proxy;
- /**
- * 要处理的对象
- */
- private Object delegate;
- /**
- * 动态生成方法被处理过后的对象(写法固定)
- *
- * @param delegate
- * @param proxy
- * @return Object
- */
- public Object bind(Object delegate, Object proxy) {
- this.delegate = delegate;
- this.proxy = proxy;
- return Proxy.newProxyInstance(
- this.delegate.getClass().getClassLoader(), this.delegate
- .getClass().getInterfaces(), this);
- }
- /**
- * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的
- */
- @SuppressWarnings("unchecked")
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- Object result = null;
- try {
- // 反射得到操作者的实例
- Class clazz = this.proxy.getClass();
- // 反射得到操作者的start方法
- Method start = clazz.getDeclaredMethod("start",
- new Class[]{Method.class});
- // 反射执行start方法
- start.invoke(this.proxy, new Object[]{method});
- // JVM通过这条语句执行原来的方法(反射机制)
- result = method.invoke(this.delegate, args);
- // 反射得到操作者的end方法
- Method end = clazz.getDeclaredMethod("end",
- new Class[]{Method.class});
- // 反射执行end方法
- end.invoke(this.proxy, new Object[]{method});
- } catch (Exception e) {
- e.printStackTrace();
- }
- // 返回方法返回值给调用者
- return result;
- }
- }
测试一下
- package demo.aop;
- public class AopTest {
- public static void main(String[] args) {
- new AopTest().test3();
- }
- public void test3() {
- IHello hello = (IHello) new DynaProxyHello2().bind(new Hello(),
- new LoggerOperation());
- hello.sayHello("wallet white");
- System.out.println("------------------");
- hello.sayGoodBye("wallet white");
- }
- }
结果输出:
- INFO Wed Jul 31 22:12:08 CST 2013 sayHellomethod start
- hello wallet white
- DEBUG Wed Jul 31 22:12:08 CST 2013 sayHellomethod end
- ------------------
- INFO Wed Jul 31 22:12:08 CST 2013 sayGoodByemethod start
- wallet white goodbye!
- DEBUG Wed Jul 31 22:12:08 CST 2013 sayGoodByemethod end
本文主要参考了下面列出的参考文献的第2篇文章
文章最后留下一个问题:如果不想让所有方法都被日志记录,我们应该怎么去解藕?
也给了一个明确的思路:将需要日志记录的方法写在配置文件里
我在这里做法如下:
---------------------------------------------------------------------------------------------
在馆内添加一个配置文件aop.xml(demo.aop)
- <?xml version="1.0" encoding="gbk"?>
- <aop>
- <clazz name="demo.aop.Hello">
- <method name="sayHello" />
- </clazz>
- <clazz name="demo.aop.Hello2">
- <method name="sayHello" />
- <method name="sayGoodBye" />
- </clazz>
- </aop>
在这里是完全匹配,由配置可以看出,对于类demo.aop.Hello内的sayHello方法和类demo.aop.Hello2内的sayHello,sayGoodBye方法添加日志记录功能
添加一个读取配置文件的工具类
- package demo.aop;
- import java.io.File;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import javax.xml.parsers.DocumentBuilder;
- import javax.xml.parsers.DocumentBuilderFactory;
- import org.w3c.dom.Document;
- import org.w3c.dom.Element;
- import org.w3c.dom.Node;
- import org.w3c.dom.NodeList;
- public class AopUtil {
- private static Map<String, List<String>> aopMap = null;
- public static Map<String, List<String>> initConfig() {
- if (aopMap != null) {
- return aopMap;
- }
- aopMap = new HashMap<String, List<String>>();
- String filePath = Class.class.getClass().getResource(
- "/demo/aop/aop.xml").getPath();
- File f = new File(filePath);
- Element element = null;
- DocumentBuilder db = null;
- DocumentBuilderFactory dbf = null;
- try {
- dbf = DocumentBuilderFactory.newInstance();
- db = dbf.newDocumentBuilder();
- Document dt = db.parse(f);
- element = dt.getDocumentElement(); // 根元素 aop
- NodeList clazzNodes = element.getChildNodes(); // 根元素下的子节点 clazz
- int cLen = clazzNodes.getLength();
- // 遍历包名.类名 clazz
- for (int i = 0; i < cLen; i++) {
- Node node1 = clazzNodes.item(i);
- if ("clazz".equals(node1.getNodeName())) {
- String clazzName = node1.getAttributes().getNamedItem(
- "name").getNodeValue();
- NodeList nodeDetail = node1.getChildNodes();
- List<String> methodList = new ArrayList<String>();
- // 遍历方法名
- for (int j = 0; j < nodeDetail.getLength(); j++) {
- Node detail = nodeDetail.item(j);
- if ("method".equals(detail.getNodeName())) {
- String methodName = detail.getAttributes()
- .getNamedItem("name").getNodeValue();
- methodList.add(methodName);
- }
- }
- aopMap.put(clazzName, methodList);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- // for (Map.Entry<String, List<String>> entry : aopMap.entrySet()) {
- // System.out.println(entry.getKey());
- // List<String> mList = entry.getValue();
- // if (mList != null)
- // for (String s : mList) {
- // System.out.println(s);
- // }
- // }
- return aopMap;
- }
- }
修改动态代理类
- package demo.aop;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.util.List;
- import java.util.Map;
- /**
- * Hello动态代理类 代理对象与被代理对像解藕
- *
- * @author ydc
- * @date 6:56:27 PM Jul 31, 2013
- */
- public class DynaProxyHello3 implements InvocationHandler {
- /**
- * 操作者
- */
- private Object proxy;
- /**
- * 要处理的对象
- */
- private Object delegate;
- /**
- * 动态生成方法被处理过后的对象(写法固定)
- *
- * @param delegate
- * @param proxy
- * @return Object
- */
- public Object bind(Object delegate, Object proxy) {
- this.delegate = delegate;
- this.proxy = proxy;
- return Proxy.newProxyInstance(
- this.delegate.getClass().getClassLoader(), this.delegate
- .getClass().getInterfaces(), this);
- }
- /**
- * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的
- */
- @SuppressWarnings("unchecked")
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- Object result = null;
- try {
- // 初始化配置文件
- Map<String, List<String>> aopMap = AopUtil.initConfig();
- // 要执行的方法所在包与类名
- String clazzName = this.delegate.getClass().getName();
- String methodName = method.getName();
- // 是否调用日志记录的标志
- boolean isOpen = false;
- for (Map.Entry<String, List<String>> entry : aopMap.entrySet()) {
- // 匹配方法所在的包名与类名
- if (clazzName.equals(entry.getKey())) {
- List<String> mList = entry.getValue();
- if (mList != null) {
- for (String m : mList) {
- if (methodName.equals(m)) {
- isOpen = true;
- break;
- }
- }
- }
- }
- }
- // 反射得到操作者的实例
- Class clazz = this.proxy.getClass();
- if (isOpen) {
- // 反射得到操作者的start方法
- Method start = clazz.getDeclaredMethod("start",
- new Class[]{Method.class});
- // 反射执行start方法
- start.invoke(this.proxy, new Object[]{method});
- }
- // JVM通过这条语句执行原来的方法(反射机制)
- result = method.invoke(this.delegate, args);
- if (isOpen) {
- // 反射得到操作者的end方法
- Method end = clazz.getDeclaredMethod("end",
- new Class[]{Method.class});
- // 反射执行end方法
- end.invoke(this.proxy, new Object[]{method});
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- // 返回方法返回值给调用者
- return result;
- }
- }
这样,再对上述代码进行测试:
- package demo.aop;
- public class AopTest {
- public static void main(String[] args) {
- new AopTest().test4();
- }
- public void test4() {
- IHello hello = (IHello) new DynaProxyHello3().bind(new Hello(),
- new LoggerOperation());
- hello.sayHello("wallet white");
- System.out.println("------------------");
- hello.sayGoodBye("wallet white");
- }
- }
结果输出:
- INFO Thu Aug 01 09:35:03 CST 2013 sayHellomethod start
- hello wallet white
- DEBUG Thu Aug 01 09:35:03 CST 2013 sayHellomethod end
- ------------------
- wallet white goodbye!
在这里我们看到 Hello类内的sayGoodBye方法没有日志记录功能。
参考:
1.http://baike.baidu.com/view/73626.htm
2.http://www.blogjava.net/DoubleJ/archive/2008/03/04/183796.html
3.http://www.cnblogs.com/200911/archive/2012/10/09/2716882.html