一、IOC基础使用(简略):
1.Spring作用:
1.生态体系庞大,全能型选手!【springmvc是其一个子模块,jdbcTemplate能直接操作数据库!】
2.将其他组件粘合在一起
3.IOC容器和AOP
Spring的Ioc机制(控制反转和依赖注入)正是用在此处。
Spring的Ioc(控制反转和依赖注入)
控制反转[Ioc]:就是由容器控制程序之间的(依赖)关系,而非传统实现中,由程序代码直接操控。
控制反转是一种思想,其具体实现就是依赖注入!
依赖注入[DI]:组件之间的依赖关系由容器在运行期决定 ,由容器动态的将某种依赖关系注入到组件之中。
2.IOC容器细节
1.使用IOC容器创建对象
2.使用IOC容器在创建对象的同时,给对象的属性赋值
3.在创建对象的过程中,实现组件对象之间的自动装配
3.搭建Spring IOC容器需要的开发环境
1.导入IOC容器需要的jar包
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
commons-logging-1.1.3.jar
2.创建Spring配置文件[Spring bean Configuration File]
1 <!--实验1:通过IOC容器创建对象,并为属性赋值 --> 2 <!-- 需要由IOC容器创建对象的全类名 --> 3 <!-- 为了便于从IOC容器中获取book对象,声明一个一个唯一的id值 --> 4 <bean id="book01" class="com.neuedu.spring.bean.Book"> 5 <property name="bookId" value="2001"></property> 6 <property name="bookName" value="三国演义"></property> 7 <property name="author" value="罗贯中"></property> 8 <property name="price" value="22.5"></property> 9 </bean> 10 11 12 @Test 13 public void test01() { 14 //1.创建IOC容器自身的对象 15 //指定配置文件以类路径为基准的配置文件名 16 ApplicationContext ioc= new ClassPathXmlApplicationContext("bean.xml"); 17 //2.从IOC容器中获取指定的对象 18 Object bean = ioc.getBean("book01"); 19 System.out.println(bean); 20 } 21 22 23 注意: 24 ①IOC容器本身对象创建时,会将配置文件中配置好的bean先创建出来 25 ②默认是单实例的,只创建bean的一个对象 26 ③如果设置bean的scope属性为prototype,那么创建bean的对象就是多实例的,在获取的时候创建,每次获取对象都会创建新的 27 ④.从IOC容器中获取对象 28 ①根据bean的id获取 29 ②根据bean的类型获取:要求容器中指定类型的bean是唯一的 30 31 bean的后置处理器: 32 1.指的是在bean的初始化方法前后执行操作的专门的对象。 33 2.自定义的后置处理器: 34 1)需要实现接口:org.springframework.beans.factory.config.BeanPostProcessor . 35 2) 做相应的配置就好!
二、IOC容器基础(详细,包含方法使用)
1 IOC和DI
①IOC(Inversion of Control):反转控制。
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
②DI(Dependency Injection):依赖注入。
IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
③IOC容器在Spring中的实现
[1]在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
[2]Spring提供了IOC容器的两种实现方式
(1)BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
(2)ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
④ApplicationContext的主要实现类
[1]ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件
[2]FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
[3]在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。
⑤ConfigurableApplicationContext
[1]是ApplicationContext的子接口,包含一些扩展方法
[2]refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。
⑥WebApplicationContext
专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
2 通过类型获取bean
从IOC容器中获取bean时,除了通过id值获取,还可以通过bean的类型获取。但如果同一个类型的bean在XML文件中配置了多个,则获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的。
HelloWorld helloWorld = cxt.getBean(HelloWorld. class); |
3 给bean的属性赋值
3.1 赋值的途经
①通过bean的setXxx()方法赋值
HelloWorld中使用的就是这种方式
②通过bean的构造器赋值
<bean id="book" class="com.neuedu.spring.bean.Book" > <constructor-arg value= "10010"/> <constructor-arg value= "Book01"/> <constructor-arg value= "Author01"/> <constructor-arg value= "20.2"/> </bean > |
●通过索引值指定参数位置
<bean id="book" class="com.neuedu.spring.bean.Book" > <constructor-arg value= "10010" index ="0"/> <constructor-arg value= "Book01" index ="1"/> <constructor-arg value= "Author01" index ="2"/> <constructor-arg value= "20.2" index ="3"/> </bean > |
●通过类型不同区分重载的构造器
<bean id="book" class="com.neuedu.spring.bean.Book" > <constructor-arg value= "10010" index ="0" type="java.lang.Integer" /> <constructor-arg value= "Book01" index ="1" type="java.lang.String" /> <constructor-arg value= "Author01" index ="2" type="java.lang.String" /> <constructor-arg value= "20.2" index ="3" type="java.lang.Double" /> </bean > |
③给bean的级联属性赋值
<bean id="action" class="com.neuedu.spring.ref.Action"> <property name="service" ref="service"/> <!-- 设置级联属性(了解) --> <property name="service.dao.dataSource" value="DBCP"/> </bean> |
④p名称空间
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。
Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean的属性。
使用p命名空间后,基于XML的配置方式将进一步简化。
<bean id="studentSuper" class="com.neuedu.helloworld.bean.Student" p:studentId="2002" p:stuName="Jerry2016" p:age="18" /> |
3.2 可以使用的值
①字面量
[1]可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
[2]基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
[3]若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来
②null值(象String、包装类等都可使用null,但对于double、int等基本类型其最终对应类型不是null)
<bean class="com.neuedu.spring.bean.Book" id="bookNull" > <property name= "bookId" value ="2000"/> <property name= "bookName"> <null/> </property> <property name= "author" value ="nullAuthor"/> <property name= "price" value ="50"/> </bean > |
③外部已声明的bean(使用ref引用外部对象)
<bean id="shop" class="com.neuedu.spring.bean.Shop" > <property name= "book" ref ="book"/> </bean > |
④内部bean
当bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean。内部bean声明直接包含在<property>或<constructor-arg>元素里,不需要设置任何id或name属性
内部bean不能使用在任何其他地方
<bean id="shop2" class="com.neuedu.spring.bean.Shop" > <property name= "book"> <bean class= "com.neuedu.spring.bean.Book" > <property name= "bookId" value ="1000"/> <property name= "bookName" value="innerBook" /> <property name= "author" value="innerAuthor" /> <property name= "price" value ="50"/> </bean> </property> </bean > |
3.3 集合属性
在Spring中可以通过一组内置的XML标签来配置集合属性,例如:<list>,<set>或<map>。
①数组和List
配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素。这些标签可以通过<value>指定简单的常量值,通过<ref>指定对其他Bean的引用。通过<bean>指定内置bean定义。通过<null/>指定空元素。甚至可以内嵌其他集合。
数组的定义和List一样,都使用<list>元素。
配置java.util.Set需要使用<set>标签,定义的方法与List一样。
<bean id="shop" class="com.neuedu.spring.bean.Shop" > <property name= "categoryList"> <!-- 以字面量为值的List集合 --> <list> <value> 历史</value > <value> 军事</value > </list> </property> <property name= "bookList"> <!-- 以bean的引用为值的List集合 --> <list> <ref bean= "book01"/> <ref bean= "book02"/> </list> </property> </bean > |
②Map
Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签。每个条目包含一个键和一个值。
必须在<key>标签里定义键。
因为键和值的类型没有限制,所以可以自由地为它们指定<value>、<ref>、<bean>或<null/>元素。
可以将Map的键和值作为<entry>的属性定义:简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义。
<bean id="cup" class="com.neuedu.spring.bean.Cup"> <property name="bookMap"> <map> <entry> <key> <value>bookKey01</value> </key> <ref bean="book01"/> </entry> <entry> <key> <value>bookKey02</value> </key> <ref bean="book02"/> </entry> </map> </property> </bean> |
③Properties
使用<props>定义java.util.Properties,该标签使用多个<prop>作为子标签。每个<prop>标签必须定义key属性
<bean class="com.neuedu.spring.bean.DataSource" id="dataSource"> <property name="properties"> <props> <prop key="userName">root</prop> <prop key="password">root</prop> <prop key="url">jdbc:mysql:///test</prop> <prop key="driverClass">com.mysql.jdbc.Driver</prop> </props> </property> </bean> |
④集合类型的bean
如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。
配置集合类型的bean需要引入util名称空间
<util:list id="bookList"> <ref bean="book01"/> <ref bean="book02"/> <ref bean="book03"/> <ref bean="book04"/> <ref bean="book05"/> </util:list> <util:list id="categoryList"> <value>编程</value> <value>极客</value> <value>相声</value> <value>评书</value> </util:list> |
4 通过工厂创建bean
4.1 静态工厂
调用静态工厂方法创建bean是将对象创建的过程封装到静态方法中。当客户端需要对象时,只需要简单地调用静态方法,而不用关心创建对象的细节。
(即工厂类本身不用创建对象(即实例化),而是通过其提供的静态方法来获取一个对象,其仅需最开始创建一次对象)
1 StaticFactory类中: 2 public class StaticFactory { 3 private static Map<String,Book> map; 4 5 static{ 6 map=new HashMap<String,Book>(); 7 map.put("book01", new Book("水浒传","施耐庵",60.0)); 8 map.put("book02", new Book("斗罗大陆","唐家三少",54.0)); 9 map.put("book03", new Book("红楼梦","曹雪芹",59.2)); 10 } 11 12 public static Book getId(String id) { 13 return map.get(id); 14 } 15 } 16 //类中全为静态
声明通过静态方法创建的bean需要在bean的class属性里指定静态工厂类的全类名,同时在factory-method属性里指定工厂方法的名称。最后使用<constrctor-arg>元素为该方法传递方法参数。
1 applicationContext.xml中: 2 <bean id="staticFactory" class="com.neuedu.spring.entity.StaticFactory" factory-method="getId"> 3 <constructor-arg value="book01"></constructor-arg> 4 </bean> 5 //<constructor-arg value="book01">,value处为上面类中map的键,对应getId方法
1 ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml"); 2 Object bean = ioc.getBean("staticFactory"); 3 System.out.println(bean); 4 //创建Junit类进行测试:在控制台显示对应Id的值 5 6 上面类中对应的Book封装对象: 7 public class Book { 8 private String bookName; 9 private String author; 10 private double price; 11 。。。。。。。 12 }
4.2 实例工厂
实例工厂方法:将对象的创建过程封装到另外一个对象实例的方法里。当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象的创建细节。
[通过实例方法提供实例对象,工厂类本身需要先创建对象!]
实现方式
①配置工厂类实例的bean
②在factory-method属性里指定该工厂方法的名称
③使用 construtor-arg 元素为工厂方法传递方法参数
1 InstanceFactory类中: 2 public class InstanceFactory { 3 private Map<String,Book> map; 4 5 { 6 map=new HashMap<String,Book>(); 7 map.put("book01", new Book("水浒传","施耐庵",60.0)); 8 map.put("book02", new Book("斗罗大陆","唐家三少",54.0)); 9 map.put("book03", new Book("红楼梦","曹雪芹",59.2)); 10 } 11 12 public Book getId(String id) { 13 return map.get(id); 14 } 15 } 16 17 applicationContext.xml中: 18 <bean id="instanceFactory" class="com.neuedu.spring.entity.InstanceFactory"></bean> 19 <bean id="bookinstanceFactory" factory-bean="instanceFactory" factory-method="getId"> 20 <constructor-arg value="book02"></constructor-arg> 21 </bean>
4.3 FactoryBean
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。
工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。
1 public class MyFactoryBean implements FactoryBean<Book>{ 2 3 @Override 4 public Book getObject() throws Exception { 5 //Spring的IOC容器就是调用该方法返回对象的 6 return new Book("斗破苍穹","天蚕土豆",43.2); 7 } 8 9 @Override 10 public Class<?> getObjectType() { 11 return null; 12 } 13 14 @Override 15 public boolean isSingleton() { 16 //是否为单实例 17 return true; 18 } 19 }
1 <bean id="book" class="com.neuedu.spring.entity.Book" init-method="init"> 2 <property name="bookName" value="仙逆"></property> 3 </bean> 4 5 <bean id="myPostBeanProccess" class="com.neuedu.spring.entity.MyPostBeanProccess"></
<bean id="myFactoryBean" class="com.neuedu.spring.entity.MyFactoryBean" init-method="init"></bean>
5 bean的高级配置
5.1 配置信息的继承
①背景
查看下面两个Employee的配置,其中dept属性是重复的。
<bean id="dept" class="com.neuedu.parent.bean.Department"> <property name="deptId" value="100"/> <property name="deptName" value="IT"/> </bean> <bean id="emp01" class="com.neuedu.parent.bean.Employee"> <property name="empId" value="1001"/> <property name="empName" value="Tom"/> <property name="age" value="20"/> <!-- 重复的属性值 --> <property name="detp" ref="dept"/> </bean> <bean id="emp02" class="com.neuedu.parent.bean.Employee"> <property name="empId" value="1002"/> <property name="empName" value="Jerry"/> <property name="age" value="25"/> <!-- 重复的属性值 --> <property name="detp" ref="dept"/> </bean> |
②配置信息的继承
<!-- 以emp01作为父bean,继承后可以省略公共属性值的配置 --> <bean id="emp02" parent="emp01"> <property name="empId" value="1002"/> <property name="empName" value="Jerry"/> <property name="age" value="25"/> </bean> |
Spring允许继承bean的配置,被继承的bean称为父bean。继承这个父bean的bean称为子bean
子bean从父bean中继承配置,包括bean的属性配置
子bean也可以覆盖从父bean继承过来的配置
③补充说明
父bean可以作为配置模板,也可以作为bean实例。若只想把父bean作为模板,可以设置<bean>的abstract 属性为true,这样Spring将不会实例化这个bean(子bean可继承父bean中属性、类,但父bean不能创建对象)
如果一个bean的class属性没有指定,则必须是抽象bean
并不是<bean>元素里的所有属性都会被继承。比如:autowire,abstract等。
也可以忽略父bean的class属性,让子bean指定自己的类,而共享相同的属性配置。但此时abstract必须设为true。
5.2 bean之间的依赖
有的时候创建一个bean的时候需要保证另外一个bean也被创建,这时我们称前面的bean对后面的bean有依赖。例如:要求创建Employee对象的时候必须先创建Department。这里需要注意的是依赖关系不等于引用关系,Employee即使依赖Department也可以不引用它。
<bean id="emp03" class="com.neuedu.parent.bean.Employee" depends-on="dept"> <property name="empId" value="1003"/> <property name="empName" value="Kate"/> <property name="age" value="21"/> </bean> |
5.3 bean的作用域
在Spring中,可以在<bean>元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。
当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作用域为prototype时,IOC容器在获取bean的实例时创建bean的实例对象。
5.4 bean的生命周期
①Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
②Spring IOC容器对bean的生命周期进行管理的过程:
[1]通过构造器或工厂方法创建bean实例
[2]为bean的属性设置值和对其他bean的引用
[3]调用bean的初始化方法
[4]bean可以使用了
[5]当容器关闭时,调用bean的销毁方法
③在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法
④bean的后置处理器
[1]bean后置处理器允许在调用初始化方法前后对bean进行额外的处理
[2]bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例。其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。
[3] bean后置处理器时需要实现接口:
org.springframework.beans.factory.config.BeanPostProcessor。在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
●postProcessBeforeInitialization(Object, String)
●postProcessAfterInitialization(Object, String)
1 public class MyPostBeanProccess implements BeanPostProcessor{ 2 3 @Override 4 public Object postProcessAfterInitialization(Object object, String beanId) throws BeansException { 5 System.out.println("postProcessAfterInitialization对象:"+object+";"+beanId); 6 return object; 7 } 8 9 @Override 10 public Object postProcessBeforeInitialization(Object object, String beanId) throws BeansException { 11 System.out.println("postProcessBeforeInitialization对象:"+object+";"+beanId); 12 return object; 13 } 14 15 }
1 <bean id="book" class="com.neuedu.spring.entity.Book" init-method="init"> 2 <property name="bookName" value="仙逆"></property> 3 </bean> 4 5 <bean id="myPostBeanProccess" class="com.neuedu.spring.entity.MyPostBeanProccess"></bean> 6 7 //对应在Book对象初始化方法 8 /*public class Book { 9 private String bookName; 10 private String author; 11 private double price; 12 public Book() { 13 super(); 14 System.out.println("Book无参构造器"); 15 } 16 private void init() { 17 System.out.println("Book的init方法执行了!"); 18 } 19 ...... 20 }*/
⑤添加bean后置处理器后bean的生命周期
[1]通过构造器或工厂方法创建bean实例
[2]为bean的属性设置值和对其他bean的引用
[3]将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
[4]调用bean的初始化方法
[5]将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
[6]bean可以使用了
[7]当容器关闭时调用bean的销毁方法
5.5 引用外部属性文件
当bean的配置信息逐渐增多时,查找和修改一些bean的配置信息就变得愈加困难。这时可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数据库的基本信息的配置。
①直接配置
<!-- 直接配置 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"/> <property name="password" value="root"/> <property name="jdbcUrl" value="jdbc:mysql:///test"/> <property name="driverClass" value="com.mysql.jdbc.Driver"/> </bean> |
②创建properties属性文件
prop.userName=root prop.password=root prop.url=jdbc:mysql:///test prop.driverClass=com.mysql.jdbc.Driver |
③引入context名称空间
④指定properties属性文件的位置
<!-- 指定properties属性文件的位置 --> <!-- classpath:xxx 表示属性文件位于类路径下 --> <context:property-placeholder location="classpath:jdbc.properties"/> |
⑤从properties属性文件中引入属性值
<!-- 从properties属性文件中引入属性值 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${prop.userName}"/> <property name="password" value="${prop.password}"/> <property name="jdbcUrl" value="${prop.url}"/> <property name="driverClass" value="${prop.driverClass}"/> </bean> |
1 测试类中: 2 @Test 3 public void test(){ 4 ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml"); 5 AController bean = ioc.getBean(AController.class); 6 System.out.println(bean); 7 } 8 9 调用外部文件中属性的类: 10 public class AController { 11 12 @Value("${jdbc.username}")//使用spring-aop-4.0.0.RELEASE.jar包 13 private String userName; 14 15 @Override 16 public String toString() { 17 return "AController [userName=" + userName + "]"; 18 } 19 } 20 21 bean中对应设置: 22 <bean id="AController" class="com.neuedu.spring.entity.AController"></bean>
5.6 自动装配
①自动装配的概念
[1]手动装配:以value或ref的方式明确指定属性值都是手动装配。
1 <!-- 属性的装配:手动装配 --> 2 <bean id="userService" class="com.neuedu.spring.bean.UserService"></bean> 3 <bean id="userAction" class="com.neuedu.spring.bean.UserAction"> 4 <property name="userService" ref="userService"></property> 5 </bean>
[2]自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。
②装配模式
[1]根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配
1 <!-- 1.按类型装配:byType --> 2 <!-- 首先检测当前bean中需要装配的属性的类型 --> 3 <!-- 然后在IOC容器中查找匹配这个类型的bean --> 4 <!-- 如果类型匹配的bean是唯一的,那么就将这个匹配的bean注入到userAction中 --> 5 6 <bean id="userService" class="com.neuedu.spring.bean.UserService"></bean> 7 <bean id="userAction" autowire="byType" class="com.neuedu.spring.bean.UserAction"></bean>
[2]根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同
1 <!-- 2.按bean的id值装配:byName --> 2 <!-- 首先检测当前bean中需要装配的属性的属性名,属性名是将setXxx()方法去掉set,首字母小写得到的 --> 3 <!-- 然后根据属性名作为id的值,在IOC容器中查找对应的bean --> 4 <!-- 如果能够找到,则将找到bean注入进去 --> 5 <bean id="userservice" class="com.neuedu.spring.entity.Userservice"></bean> 6 <bean id="userAction" autowire="byName" class="com.neuedu.spring.entity.UserAction"></bean>
[3]通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。
③选用建议
相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。