通过前面两章,我们了解了Spring中通过控制反转和依赖注入,将bean的创建及初始化交给Spring容器,需要时直接从容器中获取。不难发现,之前我们获取bean之前都要初始化Spring容器。
但是,每次初始化Spring容器得到的容器对象是不同的,不同容器对象创建的bean也是不同的,这不仅不太符合我们预期,也消耗了资源,降低了性能。
所以,当我们在Web项目中整合Spring时,也需要想办法保证Spring容器对象的唯一。
一、Web项目整合Spring
1、分析
在Web服务运行中,ServletContext是唯一的,所以我们可以在监听到ServletContext创建的时候就创建Spring容器,并将Spring容器对象放在ServletContext的属性中。这样,我们就将Spring容器与Web Servlet容器绑定在一起,由Web容器管理Spring容器的创建和销毁。
2、配置
通过分析我们知道关键在于监听ServletContext,而这个监听器并不需要我们手动创建,Spring中已经提供了一个名为ContextLoaderListener的监听器,它位于spring-web包中。
(1)导入jar包
(2)配置监听器
在web.xml中配置监听器。(注意是在web.xml中配置,而不是spring的xml配置文件)
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
(3)构建dao层、service层及相应的dao和service类
public interface UserDao {
void save();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("Dao层保存一条user记录");
}
}
在service类中,由于需要将dao类bean注入,并且我们用setter注入的方式注入,所以这里需要加上userDao属性的setter方法。
public interface UserService {
void addUser();
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
//为了setter方式注入userDao,所以加上了setter方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser() {
System.out.println("service层新增一个user");
userDao.save();
}
}
(4)在spring配置文件中配置bean
<bean id="userDao" class="dao.impl.UserDaoImpl" ></bean>
<bean id="userService" class="service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
(5)编写sevlet测试
@WebServlet("/api/user")
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response){
//使用这种方式获取容器对象
WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getWebApplicationContext(request.getServletContext());
UserService userService = (UserService) webApplicationContext.getBean("userService");
userService.addUser();
}
}
如果这时运行tomcat发现报错了,且是下面图中的报错内容:
不要慌,报错内容其实已经说的很明白,原来会默认寻找位于WEB-INF下名为applicationContext.xml的配置文件。而此时有两种解决办法:
方法一: 直接按报错提示解决
将配置文件名改为applicationContext.xml,并移动至WEB-INF目录下。
这个解决办法缺点很明显,必须使用固定的配置文件名,并放在固定位置。
方法二: 通过web.xml中全局参数指定配置文件所在的路径
<!-- 当配置文件在src目录下时,在classpath:后面加上相对路径即可 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:Application.xml</param-value>
</context-param>
或者
<!-- 当配置文件在web目录下时,直接写配置文件的相对路径即可 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/Application.xml</param-value>
</context-param>
这样就能正常启动tomcat了。然后我们访问servlet路径/api/user,可以看到打印了dao层和service层的内容。
二、注解的使用
上面的案例中我们是将dao和service在xml配置文件中注册bean的,但一个大型项目中会有很多的dao和很多的service,如果都像上面一样在xml中注册bean的话,我们会得到一个异常臃肿、不方便维护的配置文件。针对这种情况,我们可以采用注解的方式来配置bean。
1、注解方式配置bean
spring2.5引入 @Component 注解,如果放置到类的上面,相当于在spring容器注册了bean。除了@Component注解,还有三个和它用法相同的注解,功能基本是相同的:
@Service用于标注业务层组件、(如Service层);
@Controller用于标注控制层组件(如struts中的action层);
@Repository用于标注数据访问组件,(如DAO层组件);
而@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。这里我们用@Component做实例,说一下这几个注解的用法。
(1)在接口实现类上加上注解
@Component注解有默认属性value:
没有值时,bean的名字就是首字母小写后的实现类的类名,即userDaoImpl;
有值时,相当于用这个值给bean取了名字,即userDao。
@Component
public class UserDaoImpl implements UserDao {
或
@Component("userDao")
public class UserDaoImpl implements UserDao {
(2)在spring配置文件中开启注解扫描
<!-- base-package表示注解扫描的范围,这里表示扫描com.tq这个包下的组件 -->
<context:component-scan base-package="com.tq"></context:component-scan>
(3)在servlet中测试
注意获取bean时,bean的名字要与上面@Componnet注解时相匹配。
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
//当@Component注解中没有value值时,bean的名字就是首字母小写后的实现类的类名,即userDaoImpl
//UserDao userDao = webApplicationContext.getBean("userDaoImpl", UserDaoImpl.class);
//当@Component注解中有value值时,相当于用这个值给bean取了名字,即userDao
UserDao userDao = webApplicationContext.getBean("userDao", UserDaoImpl.class);
userDao.save();
启动tomcat后,如果出现下面的报错,那么就需要引入spring-aop的jar包。
Caused by: java.lang.ClassNotFoundException: org.springframework.aop.TargetSource
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1309)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1138)
... 71 more
然后测试,发现控制台输出如下,说明注解生效了,验证完成。
Dao层保存一条user记录
2、注解方式实现属性依赖注入
2.1、@Value注解
(1)简单类型显示注入
@Value("Jack")
private String name = "LiLei"; //注入的值会覆盖初始值,name为 Jack
@Value("20")
private Integer age ; //name的值为20
@Override
public void addUser() {
System.out.println("service层新增一个user");
System.out.println("name:"+name);
System.out.println("age:"+age);
userDao.save();
}
(2)bean类型属性的注入
格式:@Value("#{要注入的bean名称}")
注意:bean名称必须是已经注册进Spring容器(xml方式配置或者注解方式配置都可以)的bean的名字,而且bean的类型必须与被注入属性的类型一致。
@Value("#{userDao}")
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
或
private UserDao userDao;
@Value("#{userDao}")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
(3)结合SpEL从配置文件中读取值并注入
新建properties配置文件config.properties,并填写内容。
将配置文件通过util标签配置在Spring配置文件中。
<util:properties id="config" location="classpath:config.properties"></util:properties>
注入属性。
@Value("#{config.name}")
private String name = "LiLei";
@Value("#{config.age}")
private Integer age ;
2.2、@Autowired注解
关于@Autowired注解,需要注意以下几点:
(1)@Autowired默认根据类型来匹配
(2)标注@Autowired的bean,如果在启动时没有找到匹配的bean会报错,可以加required=false来避免
(3)标注@Autowired的bean,如果在启动时找到了多个类型匹配的bean也会报错
(4)如果需要@Autowired注解根据名字匹配,例如存在多个相同类型但是bean名称不同的bean,需要匹配其中一个,那么可以配合@Qualifier注解一起使用。@Qualifier注解必须与@Autowired注解联合使用,单独使用时虽然不会报错,但无法实现注入,得到的属性值将为null。
@Autowired
@Qualifier("userDao")
private UserDao userDao;
或
private UserDao userDao;
@Autowired
@Qualifier("userDao")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
或
private UserDao userDao;
@Autowired
public void setUserDao( @Qualifier("userDao")UserDao userDao) {
this.userDao = userDao;
}
2.3、@Resource注解
关于@Resource注解,需要注意以下几点:
(1)默认根据名称匹配,然后才根据类型匹配
(2)标注@Resource的bean,如果在启动时没有找到匹配的bean会报错
(3)标注@Resource的bean,如果在启动时找到了多个类型匹配的bean也会报错
@Resource
private UserDao userDao;
或
private UserDao userDao;
@Resource
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
2.4、@Inject注解(一般不用)
@Inject注解的使用需要导入javax.inject 的jar包,即可以单独使用,也可以和@Named注解联合使用。注意@Named注解只能和@Inject注解联合使用,不能单独使用。
@Inject //默认按照类型注入
private UserDao userDao;
或
@Inject //默认按照类型注入
@Named("userDao") //按照名字注入,必须配合@Inject使用
private UserDao userDao;
3、Bean的其他属性的注解配置方式
3.1、@PostConstruct 注解
标明初始化方法,相当于 init-method 指定初始化方法。
3.2、@PreDestroy 注解
标明销毁方法,相当于 destroy-method 指定对象销毁方法,注意销毁方法是在关闭容器时才会调用。
3.3、@Scope注解
指定Bean的作用域,默认是singleton,即单例。
3.4、@Lazy注解
指定Bean是否要延迟加载,默认value是true,即要延迟加载。
@Component
@Scope
@Lazy
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("Dao层保存一条user记录");
}
@PostConstruct
public void init(){
System.out.println("初始化了userDao");
}
@PreDestroy
public void destroy(){
System.out.println("即将销毁userDao");
}
}
三、XML和注解混合使用
xml和注解是可以混合使用的,例如使用xml来配置bean,用注解来注入bean。
四、Spring的Junit测试集成
Spring提供了spring-test这个jar包,可以整合junit。可以简化测试代码(不需要手动创建上下文,即手动创建spring容器)。
1、导入spring-test和junit的jar包
需要注意的是,如果引入的junit包的版本在4.11以上时,由于jar包中不再包含hamcrest,所以需要再引入hamcrest的jar包 hamcrest-core-1.3.jar。
否则会报错ClassNotFoundException:org/hamcrest/SelfDescribing。
Exception in thread "main" java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
......
Caused by: java.lang.ClassNotFoundException: org.hamcrest.SelfDescribing
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 21 more
2、编写测试类代码
2.1、@RunWith注解
在测试类上使用@RunWith注解,开启了Spring的注解。
2.2、@ContextConfiguratio
在测试类上使用@ContextConfiguration注解加载核心配置文件,自动构建spring容器。这里根据Spring配置文件的位置不同(是否在类路径src下)有不同的写法。
(1)不在类路径src下
(2)在类路径src下
3、运行测试类方法
这里直接运行测试类中test01()方法,就能开始测试了。