8.基于注解与Java Config配置IoC容器
基于注解的优势:
拜托繁琐的XML形式的bean与依赖注入配置。基于”声明式“的原则,更适合轻量级的现代企业应用。让代码可读性变得更好,研发人员拥有更好的开发体验。
Spring三类注解:
组件类型注解:声明当前类的功能与职责。
自动装配注解:根据属性特征自动注入对象
元数据注解:更细化的辅助IoC容器管理对象
组件类型注解
四种组件类型注解
这些注解如果要被Spring识别的话,还要配置开启组件扫描:
<context:component-scan base-package="项目的包名"> <!--可选,排除不想被扫描的包--> <context:exclude-filter type="regex" expression="包名"/> </context:component-scan>
下面用一个案例来演示一下组件注解:
创建一个项目,配置好依赖后,在resources目录下创建applicationContext.xml文件。你可可能会有疑问,为什么明明用注解了,还要这个xml文件干什么。这是因为一些最基本的配置,我们还是要写在这个xml配置文件中。而且基于注解的xml约束和之前的xml约束不太一样,我们要复制官网文档1.9的https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-annotation-config的注解的schema配置。
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="扫描注解的包名"/> </beans>
下面以是一个案例:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.haiexijun"/> </beans>
我们指定扫描com.haiexijun包下面的spring注解。
然后,在dao包下面创建一个UserDao的类,我们对其使用@Repository注解。
package com.haiexijun.ioc.dao; import org.springframework.stereotype.Repository; //组件类型注解默认的beanId为首字母小写(如:下面UserDao的beanId默认为userDao) //也可以向@Repository()被传入自定义的beanId(如:@Repository("udao")) @Repository public class UserDao { public UserDao(){ } }
只需要在类名前面书写@Repository就行,这里要注意一个点,组件类型注解默认的beanId为首字母小写(如:下面UserDao的beanId默认为userDao)
我们编写main方法演示一下:
package com.haiexijun.ioc; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringApplication { public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); String[] ids=context.getBeanDefinitionNames(); for (String id:ids){ System.out.println(id+":"+context.getBean(id)); } } }
这个main方法打印了IoC容器内创建的所有的bean对象,运行结果如下:
注意,这里没有给UserDao设置beanId,所以spring会默认使他的beanId为首字母小写。
我们可以用@Repository(“udao”)为其设置我们自定义的beanId
此时的运行结果如下:
如果要给service业务逻辑类添加注解,就用@Service注解
package com.haiexijun.ioc.service; import org.springframework.stereotype.Service; @Service public class UserService { }
如果要对控制器用注解,就用@controller注解
package com.haiexijun.ioc.controller; import org.springframework.stereotype.Controller; @Controller public class UserController { }
如果要为一个工具类添加注解,spring并没有为工具类的注解,所以用@Component这个注解就可以
package com.haiexijun.ioc.utils; import org.springframework.stereotype.Component; @Component public class StringUtils { }
所有的注解,都可以为其设置自定义的beanId。而且这些bean在容器中都是单例的。
自动装配注解
两种自动装配注解,自动装配注解就是为了让我们在IoC容器运行的过程中自动地为某个属性注入数据,是为依赖注入所存在的。
在行业中按类型装配不推荐使用,更多鼓励按名称装配。@Named要于前面的@Inject要配合使用。@Resource注解会先按名称进行依赖注入,但名称不满足时,会再按照类型来进行依赖注入。@Resource是功能最强大的自动装配注解了。
下面进入代码演示的环节:
我们回到上面那个案例,再学习完MVC以后我们都知道,作为MVC是采用分层的方式依次地逐层进行调用。就是controller依赖于service,而service依赖于dao。
下面来简单了解一下@Autowired使用就行
package com.haiexijun.ioc.service; import com.haiexijun.ioc.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired private UserDao userDao; }
@Autowired注解用于要注入的属性上使用就行。要注意,这种注解是按类型来注入属性的,如果有两个类的实现于UserDao接口的话,它就会报错,解决方法,是把实现类的@Repository注解去掉,或在要注入的类的@Repository注解后添加一个@primary注解就行,@Autowired会优先注入有@primary注解的类。
下面要演示一下@Resource注解的基本使用:
如果@Resource注解设置name属性,则按name在IoC容器中注入。如果@Resource注解没有设置name属性,会以属性名作为bean的name在IoC容器中匹配bean,如果有匹配,则注入。按属性名未匹配,则按类型进行匹配,同@Autowired一样,需要加入@Primary注解来解决类型冲突。使用建议:在使用@Resource时推荐设置name或者保证属性名与bean名称一致。
package com.haiexijun.ioc.service; import com.haiexijun.ioc.dao.UserDao; import javax.annotation.Resource; import org.springframework.stereotype.Service; @Service public class DepService { @Resource private UserDao userDao; }
无论是@Autowired还是@Resource,它们都可以基于不使用setter方法来完成对象的注入。他们的本质都是在运行时,采用反射机制,将要注入的属性从private改为public,再完成属性的直接赋值,赋值完以后,再将其改回到private。
元数据注解
它的作用就是为Spring IoC容器管理对象时提供一些辅助信息。
@Scope和之前xml里的scope是一个用法,设置单例和多例。singleton和prototype。
这里不方便演示,以后项目中回顾。
9.基于Java Config配置Spring IoC容器
Java Config是再Spring3.0以后推出的一种全新的配置方式,他的主要原理是使用Java代码来替代传统的XML文件。
基于Java Config的优势:完全拜托了XML的束缚,使用独立Java类管理对象与依赖。注解相对分散,利用Java Config可以对配置集中管理。可以在编译时进行依赖检查,不容易出错。基于Java Config的注解配置拥有更好的开发体验,而基于xml的配置则拥有更好的可维护性。我们在实际项目中根据不同的情况,选择不同的配置方案。
Java Config核心注解
重新创建一个工程来进行演示基本使用:
分别创建dao,service,controller包,然后创建对应的java类,如下:
UserDao.java
package com.haiexijun.dao; public class UserDao { }
UserService.java
package com.haiexijun.service; import com.haiexijun.dao.UserDao; public class UserService { private UserDao userDao; //作为要使用Java Config,要保留getter和setter public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
UserController.java
package com.haiexijun.controller; import com.haiexijun.service.UserService; public class UserController { private UserService userService; public UserService getUserService() { return userService; } public void setUserService(UserService userService) { this.userService = userService; } }
要使用Java Config,要保留其getter和setter方法。
然后只要再创建一个Config类来替代xml进行配置:
用@configuration注解来标识这是一个Config配置类,里面把要管理的bean用@bean注解标注,Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名。
package com.haiexijun; import com.haiexijun.controller.UserController; import com.haiexijun.dao.UserDao; import com.haiexijun.service.UserService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //当前类是一个配置类,用于替代applicationContext.xml @Configuration public class Config { //Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名 @Bean public UserDao userDao(){ UserDao userDao=new UserDao(); return userDao; } @Bean public UserService userService(){ UserService userService=new UserService(); return userService; } @Bean public UserController userController(){ UserController userController=new UserController(); return userController; } }
然后在main方法里面编写代码演示:
package com.haiexijun; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class SpringApplication { public static void main(String[] args) { //这里使用创建AnnotationConfigApplicationContext对象来初始话IoC容器 ApplicationContext context=new AnnotationConfigApplicationContext(Config.class); String[] ids= context.getBeanDefinitionNames(); for (String id:ids){ System.out.println(id+":"+context.getBean(id)); } } }
这里不再是使用ClassPathXmlApplicationContext来创建IoC容器,而是使用AnnotationConfigApplicationContext来创建IoC容器了。然后我便利了一下IoC容器管理的Bean的相关信息,运行结果如下:
上面只是介绍了如何配置使IoC容器管理Bean,下面来学习配置对象依赖注入。
对上面的Config类的代码进行更改:
package com.haiexijun; import com.haiexijun.controller.UserController; import com.haiexijun.dao.UserDao; import com.haiexijun.service.UserService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //当前类是一个配置类,用于替代applicationContext.xml @Configuration public class Config { //Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名 @Bean public UserDao userDao(){ UserDao userDao=new UserDao(); System.out.println("已创建userDao"); return userDao; } @Bean //会按name尝试注入,name不存在则按类型注入 public UserService userService(UserDao userDao){ UserService userService=new UserService(); System.out.println("已创建userServices"); userService.setUserDao(userDao); System.out.println("调用setUserDao"+userDao); return userService; } @Bean public UserController userController(UserService userService){ UserController userController=new UserController(); System.out.println("已创建userController"); userController.setUserService(userService); System.out.println("调用setUserService"+userService); return userController; } }
要实现依赖注入,只需要在@Bean的方法里面传入要注入的对象。它会按name尝试注入,name不存在则按类型注入。
然后编写main方法测试:
package com.haiexijun; import com.haiexijun.controller.UserController; import com.haiexijun.dao.UserDao; import com.haiexijun.service.UserService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //当前类是一个配置类,用于替代applicationContext.xml @Configuration public class Config { //Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名 @Bean public UserDao userDao(){ UserDao userDao=new UserDao(); System.out.println("已创建userDao"); return userDao; } @Bean //会按name尝试注入,name不存在则按类型注入 public UserService userService(UserDao userDao){ UserService userService=new UserService(); System.out.println("已创建userServices"); userService.setUserDao(userDao); System.out.println("调用setUserDao"+userDao); return userService; } @Bean public UserController userController(UserService userService){ UserController userController=new UserController(); System.out.println("已创建userController"); userController.setUserService(userService); System.out.println("调用setUserService"+userService); return userController; } }
运行结果如下: