懒人公司里,所有人都非常的懒惰,唯独他们的程序员还在拼命的奋斗。这一天老板想喝咖啡。
public void boss(){System.out.println("我想喝咖啡");}
这时候老板走到了咖啡机。
public void cofo_jiqi(){System.out.println("我是咖啡机");}
但是老板现在并不会泡咖啡(别问我为什么不会,这是个蠢货懒老板)这时我教给他了泡咖啡的方法。
public void boss(){System.out.println("我想喝咖啡");//泡咖啡的方法Cofo cofo = new Cofo();cofo.cofo_jiqi();}
好啦,老板成功的泡好了一杯咖啡。但是没过多久,他又想喝咖啡了。由于这是懒人公司,所以老板也懒到极致,他叫了自己手下唯一勤奋的程序员:小王,去让他帮自己泡咖啡。
chengxuyuan Mr_wang = new chengxuyuan();
程序员小王叫过来了
小王被老板忽悠的团团转,屁颠屁颠的要帮老板泡咖啡。
chengxuyuan Mr_wang = new chengxuyuan();Mr_wang.pao_cofo();
可是小王的技能只有写代码,我们还要教会他泡咖啡才行。
class chengxuyuan{public void Write(){System.out.println("我会写代码");}public void pao_cofo(){System.out.println("我会泡咖啡");Cofo cofo = new Cofo();cofo.cofo_jiqi();}}
好了,现在我们教会小王泡咖啡了。这时候这个懒人老板的得力泡咖啡助手就是小王了。
讲了这么多,其实经过这个例子,我们就已经明白了iOC中控制反转的概念了。之前老板想泡咖啡,必须自己来。
老板需要知道,这个咖啡机是怎么用的,但是老板怎么会连这种低级的东西都学呢!!!所以这时候我们就需要用一个工具人。
这时候老板只需要张张嘴,让小王这个工具人来做,自己不需要动,也不需要知道咖啡机怎么用,都让小王来帮你,你需要做的就是躺在座椅上,饭来张口。我们做老板可能都喜欢这种潇洒的生活,程序也不例外。
1.控制反转
光听名字,大家应该就可以听出个一二,控制反转嘛,不就是本来属于自己控制的事情,反过来让其他人帮你控制。其实话粗理不粗,控制反转的意思大可这样理解。即通过控制传递给我代码,但实际上我并没有直接依赖于它们。这并非我的代码调用了什么更通用的代码,而是框架允许我插入自定义的行为。像这样系统设计使用的就是所谓的控制反转(简称IOC)。
当然肯定有人会想,这和我平常new对象有什么区别什么好处呢。当然我上面说的例子中,这些好处肯定不足以什么,那我就仔细讲讲他的好处。
通过解耦改善模块化
在我们的实际开发过程中,都是一块大肉多人分,一个项目多人做,这样的就是模块化。要是我们平常用new来获取对象,就大大增加了代码的耦合。让两个类变的互不可缺,这个是很不应该的。假如后期的项目,各类开始分层,挪动,到时候要是还是用new的话,必定会出现很多错误。这样的话,程序员不可能一个个找错误,那太麻烦了。所以这时候我们的IOC容器就会帮我们自动找类,来给你注入这个对象。
不用考虑创建对象的过程,只需要找IOC容器拿对象
我们可以把我们的小王当做一个ioc容器。我们只需要考虑的是,喝什么咖啡,而不用考虑这个咖啡是怎么泡出来的。大大的节省了代码量和逻辑。
那我们该如何创建一个IOC容器,让他帮我们管理对象呢。我们用Spring来做这个例子。
配置bean
就是将我们的对象信息,放入bean里面
<bean id = "chengxuyuan" class="com.sun.been.chengxuyuan"></bean><bean id ="cofo" class="com.sun.been.Cofo"></bean>
ID放的是我们的对象名字,类似A a = new A();里的 a。
class就是这个类的地址。
那么这两个对象有了,我们想想,还需要写什么吗??对了,我们既然要让小王帮我们泡咖啡,那我们必须要学会怎么跑咖啡啊,这时候我们给他写个泡咖啡的方法。
<bean id = "chengxuyuan" class="com.sun.been.chengxuyuan"><property name="cofo" ref="cofo"></property></bean><bean id ="cofo" class="com.sun.been.Cofo"></bean>
好了,那我们来看看,代码该如何获取到这些配置的呢。
首先我们要从IOC容器中获得程序员小王这个对象。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");chengxuyuan Mr_wang = applicationContext.getBean("chengxuyuan",chengxuyuan.class);
这个是个固定用法,意思是获得一个容器对象ApplicationContext,让他读取bean.xml里的配置。接下来,我们用容器的对象,将程序员小王给获取出来,之所以要用chengxuyuan.class,是因为传过去的应该是个字节码对象。需要进行类型转换。当然也可以在前面写个强转,两者都可实现。
这时候小王叫来了,要让小王泡咖啡了,但是小王这时候还是不会的,我们需要教他,这时候我们就需要用DI来注入这个方法。
public void setCofo(Cofo cofo) {this.cofo = cofo;}private Cofo cofo;
这时,你肯定就有疑惑了,这样写不会空指针异常吗,因为我们没有给对象给到cofo啊。no!no!no!这里我们的IOC容器就已经从bean.xml,找到了这个对象,并且让这个对象,等于bean.xml里我们已经配置好了的 ,cofo这个对象。所以这就是IOC容器一个强大之处,di注入。(这里是个固定用法,想让Ioc帮你注入,就只能这样写)这时候我们来运行一下
这个咖啡就已经泡好了。
那大家有没有发现,一直用bean.xml来配置对象信息,是不是太麻烦了。要是有一百个对象,那对于以后的运维都是一个困难,那我们有没有一个好的方法来简化xml呢。答案是有的,Spring专门为我们准备了注解,来完成bean.xml里的操作。我们需要使用以下几个注解:
@Autowired顾名思义,就是自动装配,其作用是为了消除代码Java代码里面的getter/setter与bean属性中的property。当然,getter看个人需求,如果私有属性需要对外提供的话,应当予以保留。
@Autowired默认按类型匹配的方式,在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。
用法就是写在你的方法的上面。
// public void setCofo(Cofo cofo) {// this.cofo = cofo;// }@Autowiredprivate Cofo cofo;
这个注解在XML中将
<property name="cofo" ref="cofo"/>
替代了。
省去了set方法,可以直接匹配方法。但是要注意,要是这个bean,有多个,再或者是没有,都会报错,因为是按类型匹配这个对象。那一旦出现了这种情况该怎么办呢?
@Resource注解与@Autowired注解作用非常相似
这是详细一些的用法,说一下@Resource的装配顺序:
(1)、@Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配
(2)、指定了name或者type则根据指定的类型去匹配bean
(3)、指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错
然后,区分一下@Autowired和@Resource两个注解的区别:
(1)、@Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
(2)、@Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了
Spring属于第三方的,J2EE是Java自己的东西,因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。
@Service
@Repository
@Component
这三个的用法是一样的,只不过是由于分层,才会出现三个注解,其底层代码都是一样的。将这个类放入IOC容器中,替代了xml中
<bean id = "chengxuyuan" class="com.sun.been.chengxuyuan"></bean><bean id ="cofo" class="com.sun.been.Cofo"></bean>
的操作。但是这样创建的bean都是单例模式,假如我们的实际开发中要用到多例模式,配置@Scope即可,默认是"singleton"即单例,"prototype"表示原型即每次都会new一个新的出来。
但是要注意通过注解来配置ioc容器,我们的bean.xml是需要添加一行标签,以此来扫描我们的注解。
<context:component-scan base-package="com.sun"></context:component-scan>
里面的参数package就是填写需要扫描注解的包。
那么现在 我们的整体代码就已经变成这样了
package com.sun.been;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.ponent;/*** SpringIoc和DI例子*/public class test {/*** 测试类入口*/public static void main(String[] args) {/*** 扫描配置文件,获取对象*/ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");chengxuyuan Mr_wang = applicationContext.getBean("chengxuyuan",chengxuyuan.class);System.out.println("我想喝咖啡");Mr_wang.pao_cofo();}}/*** 程序员类*/@Componentclass chengxuyuan{@Autowired()private Cofo cofo;public void Write(){System.out.println("我会写代码");}public void pao_cofo(){System.out.println("我会泡咖啡");cofo.cofo_jiqi();}}/*** 咖啡类*/@Componentclass Cofo{public void cofo_jiqi(){System.out.println("我是咖啡机");}}
bean.xml也只剩
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance"xmlns:context="/schema/context"xsi:schemaLocation="/schema/beans/schema/beans/spring-beans.xsd/schema/context/schema/context/spring-context-4.0.xsd"><context:component-scan base-package="com.sun"></context:component-scan></beans>
总结
使用注解之前要开启自动扫描功能,其中base-package为需要扫描的包(含子包)。
<context:component-scan base-package=“cn.test”/>
@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
@Scope注解 作用域
@Lazy(true) 表示延迟初始化
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
@Repository用于标注数据访问组件,即DAO组件。
@Service用于标注业务层组件、
@Controller用于标注控制层组件(如struts中的action)
@Scope用于指定scope作用域的(用在类上)
@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下:
@Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用
@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
@PostConstruct用于指定初始化方法(用在方法上)
@PreDestory用于指定销毁方法(用在方法上)
@DependsOn:定义Bean初始化及销毁时的顺序
@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
@PostConstruct 初始化注解
@PreDestroy 摧毁注解 默认 单例 启动就加载
@Async异步方法调用