在ClassPath中扫描组件
1)组件扫描(component scanning):Spring能够从classpath下自动扫描,侦测和实例化具有特定注解的组件;
2)特定组件包含:
--- @Component:基本注解,标识了一个受Spring管理的组件;
--- @Respository:标识持久层组件;
--- @Service:标识服务层(业务层)组件;
--- @Controller:标识表现层组件
3)对于扫描到的组件,Spring有默认的命名策略:使用非限定类名,第一个字母小写。也可以在注解中通过value属性值标识组件的别名。
4)当在组件上使用了特定的注解之后,还需要在Spring的配置文件中声明<context:component-scan>:
--- base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里及其子包中的所有类。
--- 当需要扫描多个包时,可以使用逗号分隔。
--- 如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类,示例:
<context:component-scan base-package="com.dx.spring.bean.componentscan" resource-pattern="autowire/*.class"></context:component-scan>
--- <content:include-filter>子节点表示要包含的目标类
--- <content:exclude-filter>子节点表示要排除在外的目标类
--- <content:component-scal>下可以拥有若干个<content:include-filter>、<content:exclude-filter>子节点。
Filter Type | Examples Expression | Description |
annotation | org.example.SomeAnnotation | 符合SomeAnnoation的target class |
assignable | org.example.SomeClass | 指定class或interface的全名 |
aspectj | org.example..*Service+ | AspectJ語法 |
regex | org\.example\.Default.* | Regelar Expression |
custom | org.example.MyTypeFilter | Spring3新增自訂Type,實作org.springframework.core.type.TypeFilter |
5)注意使用bean注解方式时,需要依赖spring-aop-xxx.jar包,否则会抛出:Caused by: java.lang.ClassNotFoundException: org.springframework.aop.TargetSource
注解配置Bean示例:
第一步:新建java project,导入spring依赖包:
第二步:新建Componet,Repository,Service,Controller组件类:
User.java ---Component
package com.dx.spring.beans.annotation.component; import org.springframework.stereotype.Component; @Component public class User { }
IUserRepository.java,UserRepositoryImpl.java --- Repository
package com.dx.spring.beans.annotation.repository; public interface IUserRepository { }
package com.dx.spring.beans.annotation.repository; import org.springframework.stereotype.Repository; @Repository(value="userRepository") public class UserRepositoryImpl implements IUserRepository { }
IUserService.java,UserServiceImpl.java --- Service
package com.dx.spring.beans.annotation.service; public interface IUserService { }
package com.dx.spring.beans.annotation.service; import org.springframework.stereotype.Service; @Service(value="userService") public class UserServiceImpl implements IUserService { }
UserController.java ---Controller
package com.dx.spring.beans.annotation.controller; import org.springframework.stereotype.Controller; @Controller public class UserController { }
第三步:新建测试类和Spring Bean配置文件:
Client.java测试类:
package com.dx.spring.beans.annotation; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.dx.spring.beans.annotation.component.User; import com.dx.spring.beans.annotation.controller.UserController; import com.dx.spring.beans.annotation.repository.UserRepositoryImpl; import com.dx.spring.beans.annotation.service.UserServiceImpl; public class Client { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-component-scan.xml"); User user = (User) ctx.getBean("user"); System.out.println(user); UserRepositoryImpl userRepository = (UserRepositoryImpl) ctx.getBean("userRepository"); System.out.println(userRepository); UserServiceImpl userService = (UserServiceImpl) ctx.getBean("userService"); System.out.println(userService); UserController userController = (UserController) ctx.getBean("userController"); System.out.println(userController); } }
新建spring bean配置文件bean-component-scan.xml
<?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-4.3.xsd"> <context:component-scan base-package="com.dx.spring.beans.annotation"></context:component-scan> </beans>
测试类执行打印信息如下:
测试resource-pattern
第一步:修改spring bean配置文件:
给spring bean配置文件中<context:component-scan>节点添加属性resource-pattern="repository/*.class",使得其只监控到包名包含repository的组件
<?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-4.3.xsd"> <context:component-scan base-package="com.dx.spring.beans.annotation" resource-pattern="repository/*.class"></context:component-scan> </beans>
第二步执行Client.java测试:
发现UserRepositoryImpl userRepository = (UserRepositoryImpl) ctx.getBean("userRepository");可以获取到bean,其他组件都没有可以从ioc容器中获取到bean。
测试context:exclude-filter
第一步:修改spring bean配置文件:
<context:component-scan base-package="com.dx.spring.beans.annotation"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository" /> </context:component-scan>
备注:exclude-filter意思是不加载配置项,其他项都可以包含。
第二步:测试结果:
除了UserRepositoryImpl userRepository = (UserRepositoryImpl) ctx.getBean("userRepository");不可以获取到bean,其他组件都可以从ioc容器中获取到bean。
测试context:include-filter
第一步:修改spring bean配置文件:
<context:component-scan base-package="com.dx.spring.beans.annotation" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" /> </context:component-scan>
备注:1)include-filter意思是加载配置项,其他项要依赖于use-default-filters属性值,该属性值为true都可以加载;false时,才不加载配置项以外的组件。
2)use-default-filters="false",必须修改为false,默认值为true。
第二步:测试结果:
除了UserRepositoryImpl userRepository = (UserRepositoryImpl) ctx.getBean("userRepository");可以获取到bean,其他组件都不可以从ioc容器中获取到bean。
组件装配:
<context:component-scan>元素还会自动注册AutowiredAnnotationBeanPostPorcessor实例,该实例可以自动装备具有@AutoWired和@Resource、@Inject注解的属性。
@Autowire注解也可以应用在数组类型的属性上,此时Spring会把所有匹配的Bean进行自动装配;
@Autowire注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,让后自动装配所有与之兼容的Bean。
@Autowire注解用在java.util.Map上时,若该Map的键值为String。那么,Spring将自动装配与之Map值类型兼容的Bean,此时Bean的名称作为键值。
Map依赖注入:
@Autowired private Map<String, BaseService> map;
这样会注入:key是bean名字;value就是所有实现了BaseService的Bean,假设使用上一篇的例子,则会得到:
{organizationService=com.sishuok.spring4.service.OrganizationService@617029,userService=com.sishuok.spring4.service.UserService@10ac73b}
List/数组注入:
@Autowired private List<BaseService> list;
这样会注入所有实现了BaseService的Bean;但是顺序是不确定的,如果我们想要按照某个顺序获取;在Spring4中可以使用@Order或实现Ordered接口来实现,如:
@Order(value = 1) @Service public class UserService extends BaseService<User> { }
@Lazy可以延迟依赖注入:
@Lazy @Service public class UserService extends BaseService<User> { } @Lazy @Autowired private UserService userService;
可以把@Lazy放在@Autowired之上,即依赖注入也是延迟的;当我们调用userService时才会注入。即延迟依赖注入到使用时。同样适用于@Bean。
@Conditional
@Conditional类似于@Profile(一般用于如我们有开发环境、测试环境、正式机环境,为了方便切换不同的环境可以使用
@Profile指定各个环境的配置,然后通过某个配置来开启某一个环境,方便切换)
,但是@Conditional的优点是允许自己定义规则。可以指定在如@Component、@Bean、@Configuration等注解的类上,以绝对Bean是否创建等。首先来看看使用@Profile的用例,假设我们有个用户模块:
1、在测试/开发期间调用本机的模拟接口方便开发;
2、在部署到正式机时换成调用远程接口;
public abstract class UserService extends BaseService<User> { } @Profile("local") @Service public class LocalUserService extends UserService { } @Profile("remote") @Service public class RemoteUserService extends UserService { }
我们在写测试用例时,可以指定我们使用哪个Profile:
@ActiveProfiles("remote") @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-config.xml") public class ServiceTest { @Autowired private UserService userService; }
这种方式非常简单。如果想自定义如@Profile之类的注解等,那么@Conditional就派上用场了;假设我们系统中有好多本地/远程接口,那么我们定义两个注解@Local和@Remote注解要比使用@Profile方便的多;如:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Conditional(CustomCondition.class) public @interface Local { } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Conditional(CustomCondition.class) public @interface Remote { }
public class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { boolean isLocalBean = metadata.isAnnotated("com.sishuok.spring4.annotation.Local"); boolean isRemoteBean = metadata.isAnnotated("com.sishuok.spring4.annotation.Remote"); //如果bean没有注解@Local或@Remote,返回true,表示创建Bean if(!isLocalBean && !isRemoteBean) { return true; } boolean isLocalProfile = context.getEnvironment().acceptsProfiles("local"); //如果profile=local 且 bean注解了@Local,则返回true 表示创建bean; if(isLocalProfile) { return isLocalBean; } //否则默认返回注解了@Remote或没有注解@Remote的Bean return isRemoteBean; } }
然后我们使用这两个注解分别注解我们的Service:
@Local @Service public class LocalUserService extends UserService { } @Remote @Service public class RemoteUserService extends UserService { }
首先在@Local和@Remote注解上使用@Conditional(CustomCondition.class)指定条件,然后使用@Local和@Remote注解我们的Service,这样当加载Service时,会先执行条件然后判断是否加载为Bean。@Profile就是这样实现的,其Condition是:org.springframework.context.annotation.ProfileCondition。可以去看下源码,很简单。
用法示例:
spring bean配置文件:
<?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-4.3.xsd"> <context:component-scan base-package="com.dx.spring.beans.annotation"> </context:component-scan> </beans>
在UserController.java中注入UserService
package com.dx.spring.beans.annotation.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import com.dx.spring.beans.annotation.service.IUserService; @Controller public class UserController { @Autowired private IUserService userService; public void add() { System.out.println("UserController add "); userService.add(); } }
UserService中注入UserRepository
package com.dx.spring.beans.annotation.service; public interface IUserService { void add(); }
package com.dx.spring.beans.annotation.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.dx.spring.beans.annotation.repository.IUserRepository; @Service(value = "userService") public class UserServiceImpl implements IUserService { @Autowired private IUserRepository userRepository; @Override public void add() { System.out.println("UserService add..."); userRepository.add(); } }
package com.dx.spring.beans.annotation.repository; public interface IUserRepository { void add(); }
package com.dx.spring.beans.annotation.repository; import org.springframework.stereotype.Repository; @Repository(value = "userRepository") public class UserRepositoryImpl implements IUserRepository { @Override public void add() { System.out.println("UserRepository add"); } }
Client.java调用测试:
package com.dx.spring.beans.annotation; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.dx.spring.beans.annotation.controller.UserController; public class Client { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-component-scan.xml"); UserController userController = (UserController) ctx.getBean("userController"); System.out.println(userController); userController.add(); } }
测试1:实现同一个接口的多个Bean注入
添加UserOracleRepositoryImpl.java
package com.dx.spring.beans.annotation.repository; import org.springframework.stereotype.Repository; @Repository public class UserOracleRepositoryImpl implements IUserRepository { @Override public void add() { System.out.println("UserOracleRepositoryImpl add"); } }
此时,从新测试执行Client.java,执行通过。UserServiceImpl.java并未抛出异常,一般情况来讲:
@Service(value = "userService") public class UserServiceImpl implements IUserService { @Autowired private IUserRepository userRepository; 。。。 }
这里的userRepository应该包含两个实现类,那么这里是如何成功找到它对应的Bean的呢?
按照userRepository的名称获取而来,因为UserRepositoryImpl.java的注册@Repository(value="userRepository"),按照这个名字而来。
测试2:使用set方法注入Bean
package com.dx.spring.beans.annotation.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.dx.spring.beans.annotation.repository.IUserRepository; @Service(value = "userService") public class UserServiceImpl implements IUserService { private IUserRepository userRepository; @Autowired public void setUserRepository(IUserRepository userRepository) { this.userRepository = userRepository; } @Override public void add() { System.out.println("UserService add..."); userRepository.add(); } }
测试3:注入Bean@Autowire上设置属性required=false
修改User为非组件类:
package com.dx.spring.beans.annotation.component; import org.springframework.stereotype.Component; //@Component public class User { }
修改UserRepositoryImpl.java注入User:
package com.dx.spring.beans.annotation.repository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.dx.spring.beans.annotation.component.User; @Repository(value = "userRepository") public class UserRepositoryImpl implements IUserRepository { @Autowired(required = false) private User user; @Override public void add() { System.out.println("UserRepository add " + user); } }
测试通过,打印信息为:
com.dx.spring.beans.annotation.controller.UserController@166fa74d
UserController add
UserService add...
UserRepository add null
测试4:注入Bean@Qualifier解决一个接口多个实现Bean的问题
修改UserRepositoryImpl.java
package com.dx.spring.beans.annotation.repository; public interface IUserRepository { void add(); }
package com.dx.spring.beans.annotation.repository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.dx.spring.beans.annotation.component.User; @Repository public class UserRepositoryImpl implements IUserRepository { @Override public void add() { System.out.println("UserRepository add " + user); } }
package com.dx.spring.beans.annotation.repository; import org.springframework.stereotype.Repository; @Repository public class UserOracleRepositoryImpl implements IUserRepository { @Override public void add() { System.out.println("UserOracleRepositoryImpl add"); } }
在UserServiceImpl.java中注入UserRepository:
package com.dx.spring.beans.annotation.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import com.dx.spring.beans.annotation.repository.IUserRepository; @Service(value = "userService") public class UserServiceImpl implements IUserService { private IUserRepository userRepository; @Autowired public void setUserRepository(IUserRepository userRepository) { this.userRepository = userRepository; } @Override public void add() { System.out.println("UserService add..."); userRepository.add(); } }
此时执行就会抛出异常:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'userController': Unsatisfied dependency expressed through field 'userService';
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService':
Unsatisfied dependency expressed through method 'setUserRepository' parameter 0;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.dx.spring.beans.annotation.repository.IUserRepository' available: expected at least 1 bean which qualifies as autowire candidate.
Dependency annotations: {}
解决方案:使用@Qualifier(value="userRepositoryImpl"),指定具体注入的Bean
package com.dx.spring.beans.annotation.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import com.dx.spring.beans.annotation.repository.IUserRepository; @Service(value = "userService") public class UserServiceImpl implements IUserService { private IUserRepository userRepository; @Autowired @Qualifier(value="userRepositoryImpl") public void setUserRepository(IUserRepository userRepository) { this.userRepository = userRepository; } @Override public void add() { System.out.println("UserService add..."); userRepository.add(); } }
此时,@Qualifier(value="userRepositoryImpl")也可以注入在set函数入参上:
package com.dx.spring.beans.annotation.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import com.dx.spring.beans.annotation.repository.IUserRepository; @Service(value = "userService") public class UserServiceImpl implements IUserService { private IUserRepository userRepository; @Autowired public void setUserRepository(@Qualifier(value="userRepositoryImpl") IUserRepository userRepository) { this.userRepository = userRepository; } @Override public void add() { System.out.println("UserService add..."); userRepository.add(); } }