本篇是“《Spring3.X企业应用开发实战》,陈雄华 林开雄著,电子工业出版社,2012.2出版”的学习笔记的第一篇,关于Spring最基础的IoC和AOP。
在日常的开发中,最近几年正在使用着Spring,过去使用过Spring.Net,从官方文档及互联网博客,看过很多Spring文章,出于各种原因,没有系统的进行Spring的学习,这次通过这本书系统的学习了Spring框架,很多知识贯穿起来,改变了一些错误理解,受益匪浅。
使用Spring的好处到底在哪里?
你得先体会无Spring是什么滋味,才能知道Spring有何好处;
POJO编程,轻量级,低侵入;
面向接口编程,DI,解耦,降低业务对象替换的复杂性;
以提高开发效率为目标,简化第三方框架的使用方式;
灵活的基于核心 Spring 功能的 MVC 网页应用程序框架。开发者通过策略接口将拥有对该框架的高度控制,因而该框架将适应于多种呈现(View)技术,例如 JSP,FreeMarker,Velocity,Tiles,iText 以及 POI。值得注意的是,Spring 中间层可以轻易地结合于任何基于 MVC 框架的网页层,例如 Struts,WebWork,或 Tapestry;
他的作者说:
Spring是一个解决了许多在J2EE开发中常见的问题的强大框架。
Spring提供了管理业务对象的一致方法并且鼓励了注入对接口编程而不是对类编程的良好习惯。
Spring的架构基础是基于使用JavaBean属性的Inversion of Control容器。然而,这仅仅是完整图景中的一部分:Spring在使用IoC容器作为构建关注所有架构层的完整解决方案方面是独一无二的。
Spring提供了唯一的数据访问抽象,包括简单和有效率的JDBC框架,极大的改进了效率并且减少了可能的错误。Spring的数据访问架构还集成了Hibernate和其他O/R mapping解决方案。
Spring还提供了唯一的事务管理抽象,它能够在各种底层事务管理技术,例如JTA或者JDBC之上提供一个一致的编程模型。
Spring提供了一个用标准Java语言编写的AOP框架,它给POJOs提供了声明式的事务管理和其他企业事务--如果你需要--还能实现你自己的aspects。这个框架足够强大,使得应用程序能够抛开EJB的复杂性,同时享受着和传统EJB相关的关键服务。
Spring还提供了可以和总体的IoC容器集成的强大而灵活的MVC web框架。
IoC
定义
IoC(控制反转:Inverse of Control)是Spring容器的内核,AOP、声明式事务等功能在此基础上开花结果。
虽然IoC这个重要概念不容易理解,但它确实包含很多内涵,它涉及代码解耦、设计模式、代码优化等问题。
因为IoC概念的不容易理解,Martin Fowler提出了DI(依赖注入:Dependency Injection)的概念用来代替IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
Spring通过一个配置文件描述Bean和Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。
Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、Bean实例代理、时间发布、资源装载等高级服务。
初始化
Bean工厂
概述
com.springframework.beans.factory.BeanFactory,是Spring框架最核心的接口,它提供了高级IoC的配置机制;使管理不同类型的Java对象成为可能;是Spring框架的基础设施,面向Spring本身。
初始化
- ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
- Resource res = resolver.getResource("classpath:com/baobaotao/beanFactory/benas.xml");
- BeanFactory bf = new XmlBeanFactory(res);
- System.out.println("init BeanFactory");
- Car car = bf.getBean("car",Car.class);
- System.out.println("car bean is ready for use!");
XmlBeanFactory通过Resource装载Spring配置信息并启动IoC容器,然后就可以通过getBean方法从IoC容器获取Bean了。
通过BeanFactory启动IoC容器时,不会初始化配置文件中定义的Bean,初始化动作发生在第一个调用时 。
对于SingleTon的Bean来说,BeanFactory会缓存Bean。
ApplicationContext
概述
com.springframework.context.ApplicationContext,建立在BeanFactory基础上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建应用;面向使用Spring的开发者,几乎所有的应用场合我们都直接使用ApplicationContext而非底层的BeanFactory
初始化
- ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"conf/beans1.xml","conf/beans2.xml"});
- Car car = ctx.getBean("car",Car.class);
ApplicationContext的初始化和BeanFactory有一个重大的区别:
后者在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean;而前者则在初始化应用上下文时就实例化所有单实例的Bean。因此ApplicationContext的初始化时间会比BeanFactory稍长一些,不过稍后的调用则没有“第一次惩罚”的问题;
另一个最大的区别,前者会利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostPrcecssor和BeanFactoryPostProcessor,并自动将他们注册到应用上下文中;而后者需要在代码中通过手工调用addBeanPostProcessor方法进行注册。这也是为什么在应用开发时,我们普遍使用ApplicationContext而很少使用BeanFactory的原因之一。
可以在beans的属性中定义default-lazy-init="true",达到延迟初始化的目的,这不能保证所有的bean都延迟初始化,因为有的bean可能被依赖导致初始化。不推荐延迟初始化。
WebApplicationContext
概述
WebApplicationContext是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化工作。
从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。
初始化
WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也即是说它必须在Web容器的前提下才能完成启动的工作。有过Web开发经验的读者都知道可以在web.xml中配置自启动的Servlet或定义Web容器监听器,借助这两者中的任何一个,我们就可以完成启动Spring Web应用上下文的工作。
所有版本的Web容器都可以定义自启动的Servlet,但只有Servlet2.3及以上版本的Web容器才支持Web容器监听器。有些即使支持Servlet2.3的Web服务器,但也不能再Servlet初始化之前启动Web监听器,比如Weblogic 8.1,Websphere 5.x,Oracle OC4J 9.0。
Web.xml里面的配置节点如下:
- <!--指定配置文件-->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- classpath*:/applicationContext.xml
- </param-value>
- </context-param>
- <!--声明Web容器监听器-->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
log4j
需要一种日志框架,我们使用Log4J,在类路径下,提供Log4J配置文件log4j.xml,这样启动Spring容器才不会报错。
对于WebApplicationContext,可以将Log4J配置文件放置在WEB-INF/classes下,这时Log4J引擎即可顺利启动。如果Log4J配置文件放置在其他位置,用户还必须在web.xml指定Log4J配置文件位置。
Spring为启动Log4J引擎提供了两个类似于启动WebApplicationContext的实现类:Log4jConfigServlet和Log4jConfigListener,不管采用哪种方式都必须保证能在在装载Spring配置文件之前先装载Log4J配置文件。
Web.xml里面的配置节点如下:
- <!--指定Log4J配置文件位置-->
- <context-param>
- <param-name>log4jConfigLocation</param-name>
- <param-value>/WEB-INF/classes/log4j.xml</param-value>
- </context-param>
- <!--声明Log4J监听器-->
- <listener>
- <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
- </listener>
使用外部属性文件
- <!-- 定义受环境影响易变的变量 -->
- <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
- <property name="ignoreResourceNotFound" value="true" />
- <property name="locations">
- <list>
- <!-- 标准配置 -->
- <value>classpath*:/config.properties</value>
- <!-- 本地开发环境配置 -->
- <value>file:/d:/conf/*.properties</value>
- <!-- 服务器生产环境配置 -->
- <value>file:/etc/conf/*.properties</value>
- </list>
- </property>
- </bean>
在基于xml的配置方式中,通过${cas.server.url}的表达式即可访问配置信息;在基于注解和基于Java类配置的Bean中,可以通过@Value("${cas.server.url}")的注解形式访问配置信息。#是引用Bean的属性值。
总结
BeanFactory、ApplicationContext和WebApplicationContext是Spring框架三个最核心的接口,框架中其他大部分的类都围绕它们展开、为它们提供支持和服务。
在这些支持类中,Resource是一个不可忽视的重要接口,框架通过Resource实现了和具体资源的解耦,不论它们位于何种介质中,都可以通过相同的实例返回。
与Resource配合的另一个接口是ResourceLoader,ResourceLoader采用了策略模式,可以通过传入资源的信息,自动选择适合的底层资源实现类,为生产对资源的引用提供了极大的便利。
Bean生命周期和作用域
Spring为Bean提供了细致周全的生命周期过程,通过实现特定的接口或通过<bean>属性设置,都可以对Bean的生命周期过程施加影响,Bean的生命周期不但和其实现的接口相关,还与Bean的作用范围有关。为了让Bean绑定在Spring框架上,我们推荐使用配置方式而非接口方式进行Bean生命周期的控制。
在实际的开发过程中,我们很少控制Bean生命周期,而是把这个工作交给Spring,采用默认的方式。
Bean的作用域:singleton,prototype,request,session,globalSession,默认是singleton。
配置方式
基于Xml配置方式中,配置文件的3种格式:完整配置格式、简化配置方式、使用p命名空间 。
基于注解配置方式中,使用到的注解符号:@Compoment,@Repository,@Service,@Controller,@Autowired(@Resource,@Inject),@Qualifier,@Scope,@PostConstruct,@PreDestroy 。
基于Java类配置方式中,使用到的注解符号:@Configuration,@Bean 。
Bean不同配置方式比较,总结如下:
配置方式 |
基于XML配置 |
基于注解配置 |
基于Java类配置 |
Bean定义 |
在XML文件中,通过<bean>元素定义Bean。如:<bean class="com.bbt.UserDao"/> |
在Bean实现类处通过标注@Component或衍生类(@Repository,@Service,@Controller)定义Bean |
在标注了Configuration的Java类中,通过在类方法上标注@Bean定义一个Bean。方法必须提供Bean的实例化逻辑。 |
Bean名称 |
通过<bean>的id或name属性定义,如:<bean id=""userDao"" class=com.bbt.UserDao/>,默认名称为:com.bbt.UserDao#0 |
通过注解的Value属性定义,如@Component("userDao")。默认名称为小写字母打头的类名(不带包名):userDao |
通过@Bean的name属性定义,如 @Bean("userDao"),默认名称为方法名。 |
Bean注入 |
通过<property>子元素或通过p命名空间的动态属性,如p:userDao-ref="userDao"进行注入 |
通过在成员变量或方法入参处标注@Autowired,按类型匹配自动注入。还可以配合使用@Qualifier按名称匹配方式呼入 |
比较灵活,可以通过在方法除通过@Autowired使方法入参绑定Bean,然后在方法中通过代码进行注入,还可通过调研配置类的@Bean方法进行注入。 |
Bean生命过程方法 |
通过<bena>的init-method和destroy-method属性指定Bean实现类的方法名最多只能指定一个初始化方法和一个销毁方法。 |
通过在目标方法上标注@PostConstruct和@PreDestroy注解指定初始化或销毁方法,可以定义任意多个 |
通过@Bean的initMethod或destroyMethod指定一个初始化或销毁方法;对于初始化方法来说,可以直接在方法内部通过代码的方式灵活定义初始化逻辑。 |
Bean作用范围 |
通过<bean>的scope属性指定,如:<bean class="com.bbt.UserDao" scope="prototype"> |
通过在类定义处标注@Scope指定,如:@Scope("prototype") |
通过在Bean方法定义处标注@Scope指定 |
Bean延迟初始化 |
通过<bean>的lazy-init属性绑定,默认为default,继承于<beans>的default-lazy-init设置,该值默认为false |
通过在类定义处标注@Lazy指定,如@Lazy(true) |
通过在类定义处标注@Lazy指定 |
适合场景 |
1.Bean实现类来源于第三方类库,如DataSource,JdbcTemplate等,因无法在类中标注注解,通过XML配置方式较好; 2.命名空间的配置,如aop,context等,只能采用基于XML的配置 |
Bean的实现类是当前项目开发的,可以直接在Java类中使用基于注解的配置 |
基于Java类配置的优势在于可以通过代码方式控制Bean初始化的整体逻辑。所以如果实例化Bean的逻辑比较复杂,则比较适合用基于Java类配置的方式 |
一般采用XML配置DataSource,SessionFactory等资源Bean,在XML中利用aop,context命名空间进行相关主题的配置,其他的自己项目中开发的Bean,都通过基于注解配置的方式进行配置,即整个项目采用“基于XML+基于注解”的配置方式,很少采用基于Java类的配置方式。
通用知识点
1.Xml有5个特殊符号:<>&"',转义字符分别为:< > & " ' ,也可以用<![CDATA[内容]]>的方式。
2.资源类型的地址前缀:class: class*: file: http:// ftp://
3.JavaBean规范规定:变量的前两个字母要么全部大写,要么全部小写
4.默认构造函数是不带参的构造函数。Java语言规定如果类中没有定义任何构造函数,则JVM自动为其生成一个默认的构造函数。反之,如果类中显式定义了构造函数,则JVM不会为其生成默认的构造函数。所以假设Car类中显式定义了一个带参的构造函数,如public Car(String brand),则需要同时提供一个默认构造函数public Car(),否则使用属性注入时将抛出异常。
AOP
AOP,Aspect Oriented Programming,面向切面编程
AOP的出现,是作为OOP的有益补充;AOP的应用场合是受限的,它一般只适合于那些具有横切逻辑的应用场合:如性能监测、访问控制、事务管理、日志记录。
OOP是通过纵向继承的机制,达到代码重用的目的;AOP通过横向抽取机制,把分散在各个业务逻辑中的相同代码,抽取到一个独立的模块中,还业务逻辑类一个清新的世界;把这些横切性的逻辑独立出来很容易,但如何将这些横切逻辑融合到业务逻辑中完成和原来一样的业务操作,才是事情的关键,这也正是AOP要解决的主要问题。
"AOP术语:连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、目标对象(Target)、引介(Introduction)、织入(Weaving)、代理(Proxy)、切面(Aspect);
AOP织入方式:编译器织入、类装载期织入、动态代理织入,Spring采用动态代理织入,AspectJ采用前两种织入方式;"
Spring采用了两种代理机制:基于JDK的动态代理和基于CGLib的动态代理;前者创建的代理对象性能差,后者创建对象花费时间长,都大约是10倍的差距,所以对于单例的代理对象或者具有实例池的代理对象,比较适合CGLib,反之适合JDK。
关于AOP技术的实现技术步骤,先不进行深入研究,其中JDK5.0开始的注解技术,是对开发效率有明显改进的,可以深入了解下;实际所有基于注解的配置方式全部来源于JDK5.0注解技术的支持。
四种切面类型:@AspectJ、<aop:aspect>、Advisor、<aop:advisor>,不要比较了,JDK5.0以后全部采用@AspectJ方式吧。这四种切面定义方式,其底层实现实际是相同的,表象不同,本质归一。
<!--开启注解 -->
<context:annotation-config />
参考:http://blog.sina.com.cn/s/blog_872758480100wtfh.html
<!-- 开启自动切面代理 -->
<aop:aspectj-autoproxy />
声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
演示JDK动态代理实现AOP的基本原理
- import java.lang.reflect.*;
- class AOPTest {
- public static void main(String[] args) throws Exception {
- HelloInterface hello = BeanFactory.getBean("HelloImpl",HelloInterface.class);
- hello.setInfo("zhangsan","zhangsan@163.com");
- }
- }
-
- interface HelloInterface {
- public String setInfo(String name,String email);
- }
-
- class HelloImpl implements HelloInterface {
- private String name;
- private String email;
- @Override
- public String setInfo(String name,String email) {
- this.name = name;
- this.email = email;
- System.out.println("\n\n===>setInfo函数内部输出此行...");
- return "OK";
- }
- }
-
- class AOPHandler implements InvocationHandler {
- private Object target;
- public AOPHandler(Object target) {
- this.target = target;
- }
- public void println(String str,Object... args) {
- System.out.println(str);
- if(args == null) {
- System.out.println("\t\t\t 未传入任何值...");
- } else {
- for(Object obj:args) {
- System.out.println("\t\t\t" + obj);
- }
- }
- }
- @Override
- public Object invoke(Object proxyed,Method method,Object[] args)
- throws IllegalArgumentException,IllegalAccessException,InvocationTargetException
- {
- //以下定义调用之前执行的操作
- System.out.println("\n\n===>调用方法名: " + method.getName());
- Class<?>[] variables = method.getParameterTypes();
- System.out.println("\n\t参数类型列表:\n");
- for(Class<?> typevariable : variables) {
- System.out.println("\t\t\t" + typevariable.getName());
- }
- println("\n\n\t传入参数值为:",args);
-
- //以下开始执行代理方法
- System.out.println("\n\n开始执行method.invoke...调用代理方法...");
- Object result = method.invoke(target,args);
-
- //以下定义调用之后执行的操作
- println("\n\n\t返回的参数为:",result);
- println("\n\n\t返回值类型为:",method.getReturnType());
- return result;
- }
- }
-
- class BeanFactory {
- public static Object getBean(String className) throws InstantiationException,IllegalAccessException,ClassNotFoundException {
- Object obj = Class.forName(className).newInstance();
- InvocationHandler handler = new AOPHandler(obj);//定义过滤器
- return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
- }
- @SuppressWarnings("unchecked")
- public static<T> T getBean(String className,Class<T> c) throws InstantiationException,IllegalAccessException,ClassNotFoundException {
- return (T)getBean(className);
- }
- }
其他参考方案:http://javatar./blog/814426/
总结
关于Spring,我个人的理解是:IoC是基础,然后其他一切带给我们编程简便性的地方,全部来源于AOP,让这种横向抽取机制,封装常用操作。比如:
加上@Transactional标记,就对方法或者类开启了事务;
加上@Cacheable标记,就对方法开启了缓存。
|