Spring工厂类介绍 在前面使用了Spring的ApplicationContext
,并通过它的getBean方法获取Spring的配置文件applicationContext.xml
,然后得到Bean的class对象。
从图中可以知道我们使用的是ApplicationContext
接口,而使用的具体实现类是ClassPathXmlApplicationContext
用来加载类路径下面的配置文件;注意到左边还有一个FileSystemXmlApplicationContext
实现类,用于加载文件系统中的文件,言外之意就是当你的配置文件不在当前的项目工程内,此时就可以使用FileSystemXmlApplicationContext
实现类去获取配置文件,接着去获取Bean对象。在老旧的Spring中使用的是BeanFactory
接口,正如你所看到的ApplicationContext
接口是它的子类自然功能就相对来说较为丰富。再有一点就是两者在生成Bean实例的时间是不同的。对于BeanFactory
接口来说是工厂实例化结束后,在调用getBean方法的时才会去创建该类的实例;而ApplicationContext
接口则是一开始加载配置文件的时候,就会将配置文件中所有通过单例模式创建的对象都给实例化,一般都会使用后者:
1 2 3 4 5 6 7 8 9 10 11 /** * Spring开发模式:读取磁盘系统中的配置文件 **/ @Test public void testThree(){ ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\Maven\\applicationContext.xml"); UserServiceImpl userServiceimpl = (UserServiceImpl) applicationContext.getBean("userService"); //调用对象的方法 System.out.println(userServiceimpl.getName()); userServiceimpl.sayHello(); }
使用XML来管理Bean 在Spring中管理Bean有两种方式:XML方式和注解方式,先介绍XML方式。在Spring中实例化Bean的方式有三种:1、使用类构造器实例化(默认无参数);2、使用静态工厂方法实例化(简单工厂模式);3、使用实例工厂方法实例化(工厂方法模式)
使用XML来管理Bean—-使用类构造器实例化(默认无参数) 在java包中新建一个com.envy.demo的文件夹,接着新建一个Bean1.java
文件:
1 2 3 4 5 6 7 8 public class Bean1 { /** * 使用无参数的类构造器实例化Bean */ public Bean1(){ System.out.println("使用无参数的类构造器实例化Bean"); } }
接着在applicationContext.xml
文件中新增一个Bean:
1 2 3 <!-- Bean实例化的三种方式 --> <!-- 第一种,使用无参数的类构造器实例化Bean --> <bean id="bean1" class="com.envy.demo.Bean1"></bean>
最后新建一个SpringBeanTest.java
的测试文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class SpringBeanTest { /** * * Bean实例化的三种方式 **/ @Test public void testOne(){ /** * 使用无参数的构造函数实例化Bean * **/ //获得Spring工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean1 bean1 = (Bean1)applicationContext.getBean("bean1"); }
运行testOne方法,发现控制台输出:
使用XML来管理Bean—-使用静态工厂方法实例化(简单工厂模式) 在java包中新建一个com.envy.demo的文件夹,接着新建一个Bean2.java
文件:
接着在该包中新建Bean2Factory.java
文件,用于作为静态工厂实例化Bean2:
1 2 3 4 5 6 public class Bean2Factory { public static Bean2 createBean2(){ System.out.println("使用静态工厂实例化Bean"); return new Bean2(); } }
然后在applicationContext.xml
文件中新增一个Bean:
1 2 3 <!-- Bean实例化的三种方式 --> <!-- 第二种,使用静态工厂实例化Bean --> <bean id="bean2" class="com.envy.demo.Bean2Factory" factory-method="createBean2"></bean>
最后在SpringBeanTest.java
测试文件中添加testTwo方法:
1 2 3 4 5 6 7 8 9 @Test public void testTwo(){ /** * 使用静态工厂实例化Bean * **/ //获得Spring工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean2 bean2 = (Bean2)applicationContext.getBean("bean2"); }
运行testTwo方法,发现控制台输出:
1 2 使用无参数的类构造器实例化Bean 使用静态工厂实例化Bean
你会发现之前的testOne方法也被执行了,这个是正常现象,因为该工厂ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
只要加载配置文件就会将该配置文件中所有的类都给实例化。
如果你不想看到testOne方法被执行输出,可以将applicationContext.xml
文件中的<bean id="bean1" class="com.envy.demo.Bean1"></bean>
给注释掉即可。
使用XML来管理Bean—-使用实例工厂方法实例化(工厂方法模式) 在java包中新建一个com.envy.demo的文件夹,接着新建一个Bean3.java
文件:
接着在该包中新建Bean3Factory.java
文件,用于作为实例工厂实例化Bean3:
1 2 3 4 5 6 7 8 9 public class Bean3Factory { /** * Bean3的实例工厂 **/ public Bean3 createBean3(){ System.out.println("使用实例工厂实例化Bean"); return new Bean3(); } }
然后在applicationContext.xml
文件中新增一个Bean:
1 2 3 4 <!-- Bean实例化的三种方式 --> <!-- 第三种,使用实例工厂实例化Bean --> <bean id="bean3Factory" class="com.envy.demo.Bean3Factory"></bean> <bean id="bean3" factory-bean="bean3Factory" factory-method="createBean3"></bean>
实例方法和静态方法的区别是前者的调用必须依赖于对象,而后者可以通过类名来调用。因此此处的bean3必须要依靠bean3Factory对象,所以先获取bean3Factory对象,接着把bean3Factory对象作为factory-bean来生成bean3对象。
最后在SpringBeanTest.java
测试文件中添加testThree方法:
1 2 3 4 5 6 7 8 9 @Test public void testThree(){ /** * 使用实例工厂实例化Bean * **/ //获得Spring工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean3 bean3 = (Bean3) applicationContext.getBean("bean3"); }
运行testThree方法,发现控制台输出:
通常情况下我们会使用第一种,也就是默认的无参数的构造函数方式来实例化Bean,除非你的类的构造非常复杂才会使用后面三种。
Bean的配置 一般而言在Bean在使用最多的就是id和name,通常装配一个Bean时指定一个id属性作为Bean的名称,注意id属性在IOC容器中必须是唯一的;name和id属性的区别不大,当id中出现一些特殊字符时,则必须使用name属性,Id属性中不能包含特殊字符。
class属性用于设置一个类的完全路径名称,主要作用是IOC容器生成类的实例。
Bean的作用域 Bean的作用域是通过scope属性来设置的,其中scope的值有以下几种:
前面两个较为常用,后面两个不常用,一般用于web项目。scope属性默认值是singleton,也就是单例模式。你可以测试一下,先创建一个Bean4.java
文件:
然后在applicationContext.xml
文件中新增一个Bean:
1 2 <!-- 测试Bean的作用域 --> <bean id="bean4" class="com.envy.demo.Bean4"></bean>
最后在SpringBeanTest.java
测试文件中添加testFour方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testFour(){ /** * 测试Bean的作用域 * **/ //获得Spring工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean4 bean41 = (Bean4) applicationContext.getBean("bean4"); Bean4 bean42 = (Bean4) applicationContext.getBean("bean4"); System.out.println(bean41); System.out.println(bean42); }
运行testFour方法,发现控制台输出:
1 2 com.envy.demo.Bean4@51c8530f com.envy.demo.Bean4@51c8530f
说明这两个对象是同一个对象,也就是scope属性默认值为singleton。
接着添加scope="prototype"
配置,然后再次运行testFour方法,发现控制台输出:
1 2 com.envy.demo.Bean4@51c8530f com.envy.demo.Bean4@7403c468
说明这是两个不同的对象,每次调用getBean方法都会生成一个新的实例。
Bean的生命周期配置 Spring初始化bean或销毁bean时,有时需要进行一些处理工作,因此spring可以在创建和销毁bean的时候调用Bean的两个生命周期方法:init-method
和destory-method
:
1 <bean id="test" class="com.envy.demo.Test" init-method="init" destroy-method="destory"></bean>
当bean被加载到容器的时候调用init
方法;当bean从容器中删除的时候调用destory
方法(注意destory
方法必须是当scope=singleton时才有效,因为当你不是单例的时候,destory
方法不知道你具体需要删除哪个实例)。前面说过scope默认是singleton,因此可以不写scope即可。
你可以测试一下,先创建一个Bean5.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Bean5 { public Bean5(){ System.out.println("Bean5被实例化了"); } public void initBean5(){ System.out.println("Bean5被初始化了"); } public void destroyBean5(){ System.out.println("Bean5被销毁了"); } }
然后在applicationContext.xml
文件中新增一个Bean:
1 2 <!-- 测试Bean的生命周期 --> <bean id="bean5" class="com.envy.demo.Bean5" init-method="initBean5" destroy-method="destroyBean5"></bean>
最后在SpringBeanTest.java
测试文件中添加testFive方法:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testFive(){ /** * 测试Bean的生命周期 * **/ //获得Spring工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean5 bean5 = (Bean5) applicationContext.getBean("bean5"); System.out.println(bean5); ((ClassPathXmlApplicationContext) applicationContext).close(); }
运行testFive方法,发现控制台输出:
1 2 3 4 Bean5被实例化了 Bean5被初始化了 com.envy.demo.Bean5@51c8530f Bean5被销毁了
Bean的生命周期完整过程
接下来通过之前的Bean5来介绍Bean的整个完整的生命周期过程。第一步:Bean对象实例化 。前面代码已经完成。第二步:封装Bean对象属性 。打开Bean5.java文件,修改代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Bean5 { private String name; public String getName() { return name; } public void setName(String name) { System.out.println("第二步:封装name属性"); this.name = name; } public Bean5(){ System.out.println("第一步:Bean5被实例化了"); } public void initBean5(){ System.out.println("Bean5被初始化了"); } public void destroyBean5(){ System.out.println("Bean5被销毁了"); } }
既然封装了某个属性,那么需要去applicationContext.xml
文件中新增property:
1 2 3 <bean id="bean5" class="com.envy.demo.Bean5" init-method="initBean5" destroy-method="destroyBean5"> <property name="name" value="这是测试完整的Bean生命周期"></property> </bean>
第三步:如果Bean实现BeanNameAware,那么必须重写setBeanName方法 。第四步:如果Bean实现BeanFactoryAware,则需重写setBeanFactory方法(或者实现ApplicationContextAware,并重写上下文对象setApplicationContext方法,两者二选一) 。
可以看出其实第三步和第四步是让我们这个Bean来了解自己在Spring容器中的信息,如名字、工厂等信息。
其实第三步就是获取Bean在Spring容器中配置信息,即name或者id值。在Bean5.java文件中让Bean5实现BeanNameAware
接口,并新增以下代码:
1 2 3 4 public void setBeanName(String name) { //这里的name其实就是applicationContext.xml中Bean的id的值 System.out.println("第三步:设置Bean的名称"+name); }
第四步是让Bean去了解工厂的信息。在Bean5.java文件中让Bean5实现ApplicationContextAware
接口,并新增以下代码:
1 2 3 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("第四步:了解工厂信息"); }
这其中的第五步和第八步是较为核心的步骤。第五步,如果存在类实现BeanPostProcessor(后处理Bean),则会执行其中的postProcessBeforeInitialization
方法。
先创建一个MyBean5PostProcessor.java
文件,去实现BeanPostProcessor接口,并重写其中的两个方法(一般该两个方法均有返回值):
1 2 3 4 5 6 7 8 9 10 11 public class MyBean5PostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException { System.out.println("第五步:这是初始化前的方法..."); return bean; } public Object postProcessAfterInitialization(Object bean, String s) throws BeansException { System.out.println("第八步:这是初始化后的方法..."); return bean; } }
既然新建了一个类,就得去applicationContext.xml
文件中新增一个Bean:
1 <bean class="com.envy.demo.MyBean5PostProcessor"></bean>
请注意该Bean没有id属性,因为通常设置id属性是可以让你在工厂或者其他地方可以使用到该类,而这个MyBean5PostProcessor类Spring则会在生成类的实例过程中会自动去调用,并不需要其他地方去引用,因此无需配置id。
第六步,如果Bean实现InitializingBean,那么必须重写afterPropertiesSet方法。让Bean5类去实现InitializingBean
接口,并重写afterPropertiesSet
方法:
1 2 3 public void afterPropertiesSet() throws Exception { System.out.println("第六步:属性设置完后就会调用"); }
第七步,执行applicationContext.xml
文件中<bean id="bean5" init-method="initBean5">
中的指定的init-method方法initBean5
。修改Bean5.java文件中的initBean5方法代码:
1 2 3 public void initBean5(){ System.out.println("第七步:Bean5被初始化了"); }
第八步,如果存在类实现BeanPostProcessor(后处理Bean),则会执行其中的postProcessAfterInitialization
方法。
第九步,执行Bean5.java
文件中自身的逻辑方法,如定义一个run方法:
1 2 3 public void run(){ System.out.println("第九步:执行自身业务逻辑方法"); }
第十步,如果Bean实现DisposableBean
接口,那么必须重写destroy
方法。让Bean5类去实现DisposableBean
接口,并重写destroy
方法:
1 2 3 public void destroy() throws Exception { System.out.println("第十步:执行Spring中的destroy方法"); }
第十一步,执行applicationContext.xml
文件中<bean id="bean5" destroy-method="destroyBean5>
中的指定的destroy-method方法destroyBean5
。修改Bean5.java文件中的destroyBean5方法代码:
1 2 3 public void destroyBean5(){ System.out.println("第十一步:Bean5被销毁了"); }
最后在SpringBeanTest.java测试文件中添加testSix方法:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testSix(){ /** * 测试Bean的完整生命周期 * **/ //获得Spring工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean5 bean5 = (Bean5) applicationContext.getBean("bean5"); bean5.run(); ((ClassPathXmlApplicationContext) applicationContext).close(); }
运行testSix方法,发现控制台输出:
1 2 3 4 5 6 7 8 9 10 11 第一步:Bean5被实例化了 第二步:封装name属性 第三步:设置Bean的名称bean5 第四步:了解工厂信息 第五步:这是初始化前的方法... 第六步:属性设置完后就会调用 第七步:Bean5被初始化了 第八步:这是初始化后的方法... 第九步:执行自身业务逻辑方法 第十步:执行Spring中的destroy方法 第十一步:Bean5被销毁了
前面也说过这里面最重要的是第五步和第八步(后处理Bean),它是在我们整个类的生成过程中需要执行的一个步骤,它可以在不修改Bean中的代码实现对类功能的增强。
BeanPostProcessor的作用 前面说过BeanPostProcessor(后处理类)可以在生成类的过程中对类进行代理,且可以对其中的方法进行增强。接下来通过面向接口的方式来演示如何增强一个类中的方法。
新建一个UserDao.java的接口文件:
1 2 3 4 5 6 7 8 9 public interface UserDao { public void findAll(); public void save(); public void update(); public void delete(); }
接着新建一个UserDaoImpl.java文件,去实现它并重写其中的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class UserDaoImpl implements UserDao { public void findAll() { System.out.println("查询用户"); } public void save() { System.out.println("保存用户"); } public void update() { System.out.println("修改用户"); } public void delete() { System.out.println("删除用户"); } }
既然新建了一个类,就得去applicationContext.xml文件中新增一个Bean:
1 <bean id="userDao" class="com.envy.demo.UserDaoImpl"></bean>
接着注释掉其中一些没有的代码,只保留以下代码:
1 2 3 <bean class="com.envy.demo.MyBean5PostProcessor"></bean> <!-- 测试BeanPostProcessor的作用 --> <bean id="userDao" class="com.envy.demo.UserDaoImpl"></bean>
最后在SpringBeanTest.java测试文件中添加testSeven方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testSeven(){ /** * 测试BeanPostProcessor的作用 * **/ //获得Spring工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) applicationContext.getBean("userDao"); userDao.findAll(); userDao.save(); userDao.update(); userDao.delete(); }
运行testSeven方法,发现控制台输出:
1 2 3 4 5 6 第五步:这是初始化前的方法... 第八步:这是初始化后的方法... 查询用户 保存用户 修改用户 删除用户
确实调用了MyBean5PostProcessor文件中的方法并进行输出。MyBean5PostProcessor中的两个方法目前只是直接返回了Bean实例,其实是可以返回一个代理对象的,因此可以先对Bean进行处理,最后返回一个代理对象即可。或者说需要对UserDaoImpl类中的save方法进行功能上增强,如增加权限控制。实现这个过程只需要在MyBean5PostProcessor类中修改postProcessAfterInitialization方法即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { System.out.println("第八步:这是初始化后的方法..."); if ("userDao".equals(beanName)) { Object proxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("save".equals(method.getName())) { System.out.println("执行对应的权限操作验证"); return method.invoke(bean, args); } return method.invoke(bean, args); } }); return proxy; } return bean; } }
最后在SpringBeanTest.java测试文件中运行testSeven方法,发现控制台输出:
1 2 3 4 5 6 7 第五步:这是初始化前的方法... 第八步:这是初始化后的方法... 查询用户 执行对应的权限操作验证 保存用户 修改用户 删除用户
看到没有我们并没有在实现类中进行任何操作,仅仅在postProcessAfterInitialization方法中进行了处理就实现了相应的功能。
DI依赖注入 spring在创建类的过程中会将类依赖的属性注入进来,接下是spring类的属性注入方式。对于类成员变量而言,注入方式有三种:1、构造函数注入;2、属性setter方法注入;3、接口注入(不常用)。但是Spring只支持前两种也就是构造函数注入和属性setter方法注入。
构造函数注入 通过构造方法注入Bean的属性值或依赖的对象,它保证了Bean实例在实例化后就可以使用。构造器注入在元素里声明的属性。
第一步:新建Bean6.java文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Bean6 { private String name; private Integer age; public Bean6(String name,Integer age){ this.name =name; this.age=age; } @Override public String toString() { return "Bean6{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
第二步:往applicationContext.xml文件中新增Bean6的信息:
1 2 3 4 5 <!-- 使用构造器函数给Bean注入属性 --> <bean id="bean6" class="com.envy.demo.Bean6"> <constructor-arg name="name" value="张三"></constructor-arg> <constructor-arg name="age" value="22"></constructor-arg> </bean>
第三步:新建一个测试方法testBeanOne:
1 2 3 4 5 6 7 8 9 @Test /** * 使用构造器函数给Bean注入属性 * **/ public void testBeanOne(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean6 bean6 =(Bean6)applicationContext.getBean("bean6"); System.out.println(bean6); }
运行结果:
1 Bean6{name='张三', age=22}
属性setter方法注入 使用属性的setter方法,并通过Spring配置文件中的元素来声明属性的名称及值。
第一步:新建Bean7.java文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Bean7 { private String id; public String getId(){ return this.id; } public void setId(String id){ this.id=id; } @Override public String toString() { return "Bean7{" + "id='" + id + '\'' + '}'; } }
第二步:往applicationContext.xml文件中新增Bean7的信息:
1 2 3 4 <!-- 属性setter方法注入 --> <bean id="bean7" class="com.envy.demo.Bean7"> <property name="id" value="001"></property> </bean>
第三步:新建一个测试方法testBeanTwo:
1 2 3 4 5 6 7 8 9 @Test /** * 属性setter方法注入 * **/ public void testBeanTwo(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean7 bean7 =(Bean7)applicationContext.getBean("bean7"); System.out.println(bean7); }
运行结果:
其实在实际中setter属性注入是用的较多的方法,而更多的被用在属性是引用对象的情况,如现在的Bean6有一个属性是Bean7,则应该怎么修改代码呢?按照下面的步骤进行:
第一步:新建Bean67.java文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Bean67 { private String name; private Integer age; private Bean7 bean7; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Bean7 getBean7() { return bean7; } public void setBean7(Bean7 bean7) { this.bean7 = bean7; } @Override public String toString() { return "Bean67{" + "name='" + name + '\'' + ", age=" + age + ", bean7=" + bean7 + '}'; } }
第二步:往applicationContext.xml文件中新增Bean67信息:
1 2 3 4 5 6 7 8 9 10 <!-- 属性setter方法注入 --> <bean id="bean7" class="com.envy.demo.Bean7"> <property name="id" value="001"></property> </bean> <bean id="bean67" class="com.envy.demo.Bean67"> <property name="name" value="张三"></property> <property name="age" value="22"></property> <property name="bean7" ref="bean7"></property> //ref中为引用对象的id或者name </bean>
第三步:新增测试方法testBeanThree:
1 2 3 4 5 6 7 8 9 @Test /** * 属性setter方法注入(参数中包含引用对象) * **/ public void testBeanThree(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean67 bean67 =(Bean67)applicationContext.getBean("bean67"); System.out.println(bean67); }
运行结果:
1 Bean67{name='张三', age=22, bean7=Bean7{id='001'}}
也就是说普通属性的注入使用name,而引用属性使用ref,这种方式在构造方法中注入也是适用的。
p名称空间属性注入 当然还可以使用p名称空间的方法来实现属性的注入,它是为了简化xml文件配置,从Spring2.5开始引入的。使用p:<属性名>="xxx"
引入常量值;使用p:<属性名>-ref="xxx"
引用其他Bean对象。
接下来使用前面介绍的Bean67进行p名称空间属性注入的演示。你只需要注释前面的配置,并使下面的配置生效即可:
1 2 3 4 5 6 <!-- 导入p名称空间 --> xmlns:p="http://www.springframework.org/schema/p" <!-- 属性p名称空间注入 --> <bean id="bean7" class="com.envy.demo.Bean7" p:id="001"></bean> <bean id="bean67" class="com.envy.demo.Bean67" p:name="李四" p:age="18" p:bean7-ref="bean7"></bean>
SpEL注入 SpEL(Spring Expression Language),使用Spring表达式语言对依赖注入进行简化。语法是#{表达式}
,通常是<bean id="xxx" value="#{表达式}">
。下面介绍一些常用的SpEL的用法:1、传递变量使用#{变量名称}
;2、传递字符串使用#{'字符串名称'}
;3、使用另一个Bean对象采用#{helloId2}
;4、使用指定名属性且使用方法#{world.print.toUpperCase()}
;5、使用静态字段或者方法#{T(java.lang.Math).PI}
等等。
下面通过product和Category这两个类来演示SpEL的属性注入。
第一步,新建Product.java文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Product { private String name; private double price; private Category category; public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } @Override public String toString() { return "Product{" + "name='" + name + '\'' + ", price=" + price + ", category=" + category + '}'; } }
以及Category.java文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Category { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Category{" + "name='" + name + '\'' + '}'; } }
第二步,往applicationContext.xml文件中新增以下代码:
1 2 3 4 5 6 7 8 9 <!-- SpEL属性注入 --> <bean id="product" class="com.envy.demo.Product"> <property name="name" value="#{'童装'}"></property> <property name="price" value="#{99.00}"></property> <property name="category" value="#{category}"></property> <!-- 注意value的值必须是引用对象Bean的id或者name属性 --> </bean> <bean id="category" class="com.envy.demo.Category"> <property name="name" value="#{'001'}"></property> </bean>
第三步,新增测试方法testBeanFour:
1 2 3 4 5 6 7 8 9 @Test /** * 属性SpELl方法注入(参数中包含引用对象) * **/ public void testBeanFour(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Product product =(Product) applicationContext.getBean("product"); System.out.println(product); }
运行结果:
1 Product{name='童装', price=99.0, category=Category{name='001'}}
现在只是单纯的使用到了引用对象的属性,接下来演示引用对象的方法注入。
可以新建一个ProductPrice.java文件:
1 2 3 4 5 public class ProductPrice { public double CalcPrice(){ return Math.random()*99; } }
接着修改applicationContext.xml文件为:
1 2 3 4 5 6 7 8 9 10 11 <!-- SpEL属性注入 --> <bean id="product" class="com.envy.demo.Product"> <property name="name" value="#{'童装'}"></property> <property name="price" value="#{productPrice.CalcPrice()}"></property> <property name="category" value="#{category}"></property> <!-- 注意value的值必须是引用对象Bean的id或者name属性 --> </bean> <bean id="productPrice" class="com.envy.demo.ProductPrice"></bean> <bean id="category" class="com.envy.demo.Category"> <property name="name" value="#{'001'}"></property> </bean>
最后运行一下测试方法testBeanFour,结果为:
1 Product{name='童装', price=21.540606263709023, category=Category{name='001'}}
复杂类型的属性注入 复杂类型是指属性为数组、List、Map、Set、Properties类型。
新建一个CollectionBean.java文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class CollectionBean { private String[] stringArray; //数组类型 private List<String> stringList; //列表类型 private Set<String> stringSet; //集合类型 private Map<String,Integer> stringIntegerMap; //map类型 private Properties properties; //属性类型 public String[] getStringArray() { return stringArray; } public void setStringArray(String[] stringArray) { this.stringArray = stringArray; } public List<String> getStringList() { return stringList; } public void setStringList(List<String> stringList) { this.stringList = stringList; } public Set<String> getStringSet() { return stringSet; } public void setStringSet(Set<String> stringSet) { this.stringSet = stringSet; } public Map<String, Integer> getStringIntegerMap() { return stringIntegerMap; } public void setStringIntegerMap(Map<String, Integer> stringIntegerMap) { this.stringIntegerMap = stringIntegerMap; } public Properties getProperties() { return properties; } public void setProperties(Properties properties) { this.properties = properties; } @Override public String toString() { return "CollectionBean{" + "stringArray=" + Arrays.toString(stringArray) + ", stringList=" + stringList + ", stringSet=" + stringSet + ", stringIntegerMap=" + stringIntegerMap + ", properties=" + properties + '}'; } }
接着注释掉applicationContext.xml文件中的其他代码,添加以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <!-- 复杂(集合)类型的属性注入 --> <bean id="collectionBean" class="com.envy.demo.CollectionBean"> <!-- 数组类型属性注入 --> <property name="stringArray"> <list> <value>数组1</value> <value>数组2</value> <value>数组3</value> </list> </property> <!-- List类型属性注入 --> <property name="stringList"> <list> <!-- 普通属性用value --> <value>List1</value> <value>List2</value> <!-- 对象属性使用ref --> <!--<ref>List3</ref>--> <!--<ref>List4</ref>--> </list> </property> <!-- Set类型属性注入 --> <property name="stringSet"> <set> <value>Set1</value> <value>Set2</value> <value>Set3</value> </set> </property> <!-- Map类型属性注入 --> <property name="stringIntegerMap"> <map> <entry key="map1" value="01"></entry> <entry key="map2" value="02"></entry> <entry key="map3" value="03"></entry> </map> </property> <!-- Properties类型属性注入 --> <property name="properties"> <props> <prop key="username">hello</prop> <prop key="password">world</prop> <prop key="update">good</prop> </props> </property> </bean>
最后新建一个测试testBeanFive方法,里面的代码如下:
1 2 3 4 5 6 7 8 9 @Test /** * 复杂类型的属性注入 * **/ public void testBeanFive(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); CollectionBean collectionBean =(CollectionBean) applicationContext.getBean("collectionBean"); System.out.println(collectionBean); }
运行结果为:
1 CollectionBean{stringArray=[数组1, 数组2, 数组3], stringList=[List1, List2], stringSet=[Set1, Set2, Set3], stringIntegerMap={map1=1, map2=2, map3=3}, properties={password=world, username=hello, update=good}}
复杂属性注入一般在进行框架整合的时候才会使用,而用的较多的属性则是properties和map。
Spring管理Bean(注解方式) 使用注解定义Bean 由于注解方式和XML配置方式差异较大,因此新建一个Spring项目SpringIOCAnnotation
。在pom.xml文件中以下依赖:
1 2 3 4 5 6 spring-context spring-core spring-beans spring-expression spring-aop(注解方式必须导入) junit
接着新建applicationContext.xml文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解扫描--> <context:component-scan base-package="com.envy.demo"/> <context:annotation-config/> </beans>
然后继续新建一个UserService.java文件,里面的代码如下:
1 2 3 4 5 6 @Component("userService") public class UserService { public String sayHello(String name){ return "hello" +name; } }
接着新建SpringDemoTest.java文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 public class SpringDemoTest { @Test public void testOne(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); String result = userService.sayHello("张三"); System.out.println(result); } }
最后执行testOne测试方法,结果如下:
Spring自2.5版本后使用注解去定义Bean,@Component
用于描述Spring框架中的Bean,当然除了@Component
外,Spring提供了3个功能基本和@Component
一样的注解。其中@Repository
用于对DAO层实现类进行标注;@Service
用于对Service层实现类进行标注;@Controller
用于对Controller层实现类进行标注,其实这三个类的作用就是细化了@Component
的作用,使得标注类的用途变得更加清楚,Spring在后续版本会对其进行增强。由于本例中的UserService文件是用于书写业务逻辑,因此此处的@Component
通常会使用@Service
来代替使用。
使用注解方式实现Spring属性注入 前面也说过属性分普通属性和引用属性,因此使用注解方式实现Spring属性注入时也存在一些区别。先介绍普通属性的注入方法。给UserService类新增一个eat方法和something属性:
1 2 3 4 5 private String something; public String eat(){ return "正在eat:"+something; }
然后在something上面使用@Value
注解:
1 2 @Value("面条") private String something;
然后新建一个测试方法testTwo,里面的代码如下:
1 2 3 4 5 6 7 @Test public void testTwo(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); String result = userService.eat(); System.out.println(result); }
运行结果为:
发现没有?此时不需要给something提供setter方法就能实现属性注入,注意如果该属性没有提供setter方法,那么这个@Value("面条")
就需要添加到属性上;如果存在setter方法则@Value("面条")
需要加载setter方法上:
1 2 3 4 @Value("面条") public void setSomething(String something){ this.something=something; }
在实际开发中这种提供简单属性值传入的方式其实并不常见,因为Service层需要调用Dao层的数据,因此需要考虑如何把Dao层的对象注入进来,这里考虑如何将Dao层中的某个方法注入到Service层中,然后被Service调用。
新建一个UserDao.java文件,里面的代码如下:
1 2 3 4 5 6 @Repository("userDao") public class UserDao { public void save(){ System.out.println("这是UserDao中的save方法"); } }
接着在UserService.java文件中使用@Autowired
来自动注入UserDao对象:
1 2 3 4 5 6 7 @Autowired private UserDao userDao; public void save(){ System.out.println("这是UserService中的save方法"); userDao.save(); }
通过@Autowired
实现了UserDao对象的自动注入后,就可以在Service中调用了。然后新建一个测试方法testThree,里面的代码如下:
1 2 3 4 5 6 @Test public void testThree(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.save(); }
运行结果为:
1 2 这是UserService中的save方法 这是UserDao中的save方法
这里我们使用了@Autowired
实现了对象的自动注入,注意@Autowired
默认使用类型注入,如果存在两个Bean类型相同,则会按照Bean的名称进行注入。什么意思,意思就是你修改了@Repository("userDao")
中的”userDao”,依然是可以实现对象注入,因为只要是UserDao类型private UserDao userDao;
及UserDao类,无论你@Repository("userDao")
括号中的字符串是什么,都是可以实现自动注入的,因为@Autowired
默认使用类型注入,它们只需要保证类型相同就可以。@Autowired
注入时可以针对成员变量或者setter方法。
当然如果你需要使用名称注入,可以通过@Autowired
的required属性用于设置一定要找到匹配的Bean的名称,然后再使用@Qualifier
注解指定注入的Bean名称:
1 2 3 @Autowired @Qualifier("userDao") private UserDao userDao;
注意此时@Qualifier("userDao")
中的userDao
必须与@Repository("userDao")
中的userDao
保持一致,否则运行会出错。即使用@Qualifier
指定Bean名称后,注解Bean必须指定相同名称。
Spring提供对JSR-250中定义@Resource
标准注解的支持。@Resource
和@Autowired
注解的功能相似,@Resource
会强制按照名称进行注入。也就是说 @Resource(name="userDao")
的作用与下面两行代码的作用是一致的:
1 2 @Autowired @Qualifier("userDao")
因此在大多数情况下我们会使用@Resource
注解,达到根据Bean名称注入的目的。
Spring的其他注解
新建一个Bean.java文件,里面的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component("bean") public class Bean { @PostConstruct public void init(){ System.out.println("this is init method"); } public void hello(){ System.out.println("this is hello method"); } @PreDestroy public void destory(){ System.out.println("this is destory method"); } }
接着新建一个测试方法testFour:
1 2 3 4 5 6 7 @Test public void testFour(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean bean = (Bean) applicationContext.getBean("bean"); bean.hello(); ((ClassPathXmlApplicationContext) applicationContext).close(); }
运行该测试方法结果如下:
1 2 3 this is init method this is hello method this is destory method
使用注解配置的Bean和配置的一样,默认作用范围都是singleton,当然还可以使用@Scope
注解用于指定Bean的作用范围。
新建Bean2.java文件,里面的代码如下:
1 2 3 @Component("bean2") public class Bean2 { }
接着新建一个测试方法testFive,代码为:
1 2 3 4 5 6 7 8 @Test public void testFive(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Bean2 bean1 = (Bean2) applicationContext.getBean("bean2"); Bean2 bean2 = (Bean2) applicationContext.getBean("bean2"); System.out.println(bean1==bean2); }
运行结果为:
1 2 this is init method true
注意只要Bean被加载到容器中时,@PostConstruct
注解的方法就会被调用。从上述运行结果可知,scope默认是singleton模式(单例模式),尝试使用@Scope
注解将Bean的作用范围变为prototype
(多例模式):
1 2 3 4 @Component("bean2") @Scope("prototype") public class Bean2 { }
然后再次运行testFive测试方法,结果为:
1 2 this is init method false
传统XML配置和注解配置混合使用 XML配置的优势是结构清晰,易于阅读;而注解方式的优点是开发迅速,属性注入更加方便。传统XML配置和注解配置混合使用需要两个步骤:1、引入context命名空间;2、在配置文件中添加context:annotation-config
标签。
为了演示传统XML配置和注解配置混合使用,先通过单纯的XML配置方式,其次是混合使用XML配置和注解配置。
新建ProductDao.java文件,里面的代码如下:
1 2 3 4 5 public class ProductDao { public void say(){ System.out.println("this is say method of ProductDao"); } }
接着新建CategoryDao.java文件,里面的代码如下:
1 2 3 4 5 public class CategoryDao{ public void say(){ System.out.println("this is say method of CategoryDao"); } }
然后新建ProductService.java文件,里面的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ProductService { private ProductDao productDao; private CategoryDao categoryDao; public void setProductDao(ProductDao productDao) { this.productDao = productDao; } public void setCategoryDao(CategoryDao categoryDao) { this.categoryDao = categoryDao; } public void say(){ System.out.println("this is say method of ProductService"); productDao.say(); categoryDao.say(); } }
接着打开applicationContext.xml文件,新增以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 <bean id="productService" class="com.envy.demo.ProductService"> <property name="categoryDao" ref="categoryDao"></property> <property name="productDao" ref="productDao"></property> </bean> <bean id="categoryDao" class="com.envy.demo.CategoryDao"> </bean> <bean id="productDao" class="com.envy.demo.ProductDao"> </bean>
最后新建测试方法testOne,方法内的代码为:
1 2 3 4 5 6 @Test public void testOne(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); ProductService productService = (ProductService) applicationContext.getBean("productService"); productService.say(); }
运行结果为:
1 2 3 this is say method of ProductService this is say method of ProductDao this is say method of CategoryDao
这是单纯的XML配置方式,接下来想让XML配置只是用来管理类,而属性注入采用注解的方式,只需将applicationContext.xml文件的property
标签内的信息注释掉,只保留以下代码:
1 2 3 4 5 6 7 8 <bean id="productService" class="com.envy.demo.ProductService"> </bean> <bean id="categoryDao" class="com.envy.demo.CategoryDao"> </bean> <bean id="productDao" class="com.envy.demo.ProductDao"> </bean>
既然是使用属性注入,那么去掉ProductService文件中的setter方法,还需要在添加@Resource
注解:
1 2 3 4 @Resource(name="productDao") private ProductDao productDao; @Resource(name="categoryDao") private CategoryDao categoryDao;
当然仅仅是这样是无法是注解生效的(因为我们对类使用了XML配置,不能配置@Component
注解),需要打开applicationContext.xml文件,添加<context:annotation-config/>
,该标签表示打开了属性注入功能。而前面由于开启了包扫描<context:component-scan base-package="com.envy.demo"/>
其实相当于不仅仅开启了属性注解,还可以使用类注解,而这个<context:annotation-config/>
标签仅仅是开启了属性注解,因此对于类注解的@Component
、@Controller
和@Serivice
等是无法使用的。
最后运行testOne方法,运行结果为:
1 2 3 this is say method of ProductService this is say method of ProductDao this is say method of CategoryDao