注解创建Bean

我们以前创建一个对象是采用XML配置的

  1. <bean id="customer" class="com.lff.domain.Customer" />

采用注解的方式创建,我们首先需要在XML配置文件配置需要扫描的包,能够扫描到所有的@Component注解

  1. <Context:component-scan base-package="com.lff.test" />

如果整个项目都没有XML文件,使用@ComponentScan 注解替代
在需要使用注解的类前面加@Component

  1. @Component
  2. public class Person {
  3. }

外界使用

  1. ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  2. Person person = (Person) ctx.getBean("person");
  3. System.out.println(person);

获取Bean时,默认使用类名的首字母小写的形式做为id当然你也可以使用注解的value属性设置Bean的id。

  1. @Component(value = "person1")
  2. public class Person {
  3. }

外界获取时,使用person1做为id。

  1. Person person = (Person) ctx.getBean("person1");

在项目中我们需要分层,比如Serlet层、Service层、Dao层。我们对不同的层如果都使用@Component当然没有问题,但不够语义化。
Servlet层我们通常使用@Controller

  1. @Controller
  2. public class LoginServlet {
  3. }

Service层使用@Service注解

  1. @Service(value = "loginService")
  2. public class LoginServiceImpl {
  3. }

Dao层使用@Repository注解

  1. @Repository("loginDao")
  2. public class LoginDaoImpl {
  3. }

当然上面@Controller、@Service、@Repository和@Component功能上并没有区别,都是为了把对象放在IOC容器里面。

一个比较全的示例

  1. @Component(value = "person1")
  2. @Scope("singleton") //单例,如果不想为单例值为prototype
  3. @Lazy //表示Scope为singleton延时加载
  4. @ComponentScan("com.lff.test") //扫描com.lff.test里面的包
  5. //@ComponentScans({
  6. // @ComponentScan("com.lff.test"),
  7. // @ComponentScan("com.lff.send")
  8. //})
  9. public class Person {
  10. }

注解实现注入

当注入的类型为Bean时,使用@Autowired注解

  1. @Component(value = "person")
  2. public class Person {
  3. private Dog dog;
  4. @Autowired
  5. public void setDog(Dog dog) {
  6. this.dog = dog;
  7. }
  8. }

@Autowired默认是根据参数的类型进行注入,看参数类型是Dog类型的然后从IOC容器里面找是否有Dog类型的对象。
以下的@Autowired写法都正确
直接放在成员变量上面

  1. @Component(value = "person")
  2. public class Person {
  3. @Autowired
  4. private Dog dog;
  5. public void setDog(Dog dog) {
  6. this.dog = dog;
  7. }
  8. }

直接通过反射去找

  1. @Component(value = "person")
  2. public class Person {
  3. @Autowired
  4. private Dog dog;
  5. }

也可以使用@Qualifier注解根据id来查找

  1. @Component(value = "person")
  2. public class Person {
  3. private Dog dog;
  4. @Autowired(required = false) //false找不到对应的Bean不会抛出异常,默认为true会抛出异常
  5. @Qualifier("dog") //根据id来找
  6. public void setDog(Dog dog) {
  7. this.dog = dog;
  8. }
  9. }

当注入的类型为基本类型(String、BigDecimal)时,使用@Value注解

  1. @Component(value = "person")
  2. public class Person {
  3. private String name;
  4. @Value("Cliff")
  5. public void setName(String name) {
  6. this.name = name;
  7. }
  8. }

name的名称可以从配置文件里面读取。

  1. @Component(value = "person")
  2. @PropertySource("person.properties") //加载配置文件
  3. //@PropertySources({
  4. // @PropertySource("配置文件1的名称"),
  5. // @PropertySource("配置文件2的名称"),
  6. // @PropertySource("配置文件3的名称"),
  7. //})
  8. public class Person {
  9. private String name;
  10. @Value("${name}") //从配置文件读取name
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. }

注解实现AOP

首先需要在XML文件配置下面的代码代替之前的

  1. <aop:aspectj-autoproxy />

上面的配置其实也可以使用@EnableAspectJAutoProxy注解替代

新建切面类,可以把要拦截的功能都写这个类里面,只需在里面增加方法即可。

  1. @Aspect //标识一个切面类
  2. @Component //这个是要加入到IOC容器里面
  3. @EnableAspectJAutoProxy
  4. public class PersonAop {
  5. @Around("within(com.lff.test..*)") //设置切入点,要求被注解的方法要有返回值,
  6. public Object log(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //方法必须传入一个ProceedingJoinPoint类型的参数,要求的
  7. System.out.println("before---------");
  8. //调用目标方法
  9. Object object = proceedingJoinPoint.proceed();
  10. System.out.println("after---------");
  11. return object;
  12. }
  13. }

一般@EnableAspectJAutoProxy注解可以写到@Configuration注解的类中,关于@Configuration注解我们后面会讲到。
外界使用

  1. ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  2. Person person = (Person) ctx.getBean("person");
  3. person.run("hahaha");

打印日志

  1. before---------
  2. hahaha
  3. after---------

也可以把切入点的代码使用空方法抽取出来

  1. @Aspect //标识一个切面类
  2. @Component //这个是要加入到IOC容器里面
  3. public class PersonAop {
  4. @Pointcut("within(com.lff.test..*)")
  5. public void pointCut(){} //抽取切入点
  6. @Around("pointCut()") //这里直接调用方法即可
  7. public Object log(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //方法必须传入一个ProceedingJoinPoint类型的参数,要求的
  8. System.out.println("before---------");
  9. //调用目标方法
  10. Object object = proceedingJoinPoint.proceed();
  11. System.out.println("after---------");
  12. return object;
  13. }
  14. }

注解替换XML文件

使用@Configuration的注解用于替换applicationContext.xml文件

  1. @Configuration
  2. public class AppConfig {
  3. }

此时AppConfig的类对象会自动放在IOC容器里面。因为@Configuration也是一个@Component注解(用之前也要被扫描到)。
可以在@Configuration修饰的类中使用@Bean注解修饰方法,创建Bean对象。

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. // @Bean("person") //设置获取时的id
  5. @Scope("prototype")
  6. @Lazy
  7. public Person getPerson(){
  8. return new Person();
  9. }
  10. }

默认情况下,方法名就是Bean的id,可以通过 @Bean(“person”)的方式设置id。
被@Bean修饰的方法,他的参数也会被自动注入(当然前提是参数的类型能够在IOC容器里面找到)
当@Bean方法的返回值是一个FactoryBean时,会通过调用FactoryBean的getObject返回生产的对象

  1. public class DogFactoryBean implements FactoryBean<Dog> {
  2. @Override
  3. public Dog getObject() throws Exception {
  4. return new Dog();
  5. }
  6. @Override
  7. public Class<?> getObjectType() {
  8. return Dog.class;
  9. }
  10. }
  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public DogFactoryBean dog1(){
  5. return new DogFactoryBean();
  6. }
  7. }

外界使用

  1. Dog dog = (Dog) ctx.getBean("dog1");

创建工厂也使用注解

以前我们使用的是XML,创建一个叫applicationContext.xml的文件,然后我们使用如下代码获取工程

  1. ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

当然如果XML文件里面的内容过多,我们也可以新建多个XML配置,只需在在applicationContext.xml文件里面导入其他XML文件即可

  1. <import resource="xxx.xml" />

我们能不能不使用XML的方式去创建工程呢?当然可以
使用注解创建工程的方式有很多种:我们只需要关注某个类有没有被扫描到、有没有被导入进来、有没有放到IOC容器里面就可以了,下面是一个具体的示例

  1. @Configuration
  2. @ComponentScan("com.lff") //扫描包
  3. //@ImportResource("applicationContext.xml") //导入XML,注解和XML混合使用时
  4. public class AppConfig {
  5. @Bean
  6. public DogFactoryBean dog1(){
  7. return new DogFactoryBean();
  8. }
  9. }

外界使用

  1. //ApplicationContext ctx = new AnnotationConfigApplicationContext("com.lff"); //传一个包
  2. ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); //传一个包
  3. Dog dog = (Dog) ctx.getBean("dog1");
  4. System.out.println(dog);

使用AnnotationConfigApplicationContext可以传入一个包名,也可以传入一个类的入口类名获取工厂对象。

Bean的创建方式

Bean创建方式总结起来有3种

  • @Component 这种方式一般适用于开发者自己写的类,创建方式比较简单比如直接new
  • @Bean 这种方式适用于创建方式比较复杂的场景,比如别人写好的类我们直接调用创建Bean

    1. @Bean
    2. public xxx dog1(){
    3. 调用其他库的代码
    4. return xxx;
    5. }
  • 直接在XML配置Bean的方式,这种方式也最为通用,什么类型的对象都能够创建

因为创建Bean有三种方式,当这三种方式同时存并且id也都相同,默认从IOC容器里面取对象的时候是以哪一种方式创建的呢?
从XML里面创建大于 @Bean 大于 @Component

证明:

  1. @Component
  2. public class Person {
  3. private String name = "@Component创建";
  4. public void setName(String name) {
  5. this.name = name;
  6. }
  7. }

XML配置

  1. <bean id="person" class="com.lff.test.Person">
  2. <property name="name" value="XML中的Bean" />
  3. </bean>

@Bean方式

  1. @Configuration
  2. @ComponentScan("com.lff") //扫描包
  3. @ImportResource("applicationContext.xml") //导入XML,注解和XML混合使用时
  4. public class AppConfig {
  5. @Bean
  6. public Person person(){
  7. Person person = new Person();
  8. person.setName("这是使用@Bean方式创建的");
  9. return person;
  10. }
  11. }

外界获取

  1. ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); //传一个包
  2. Person person = (Person)ctx.getBean("person");

发现person的name是XML中的Bean,可以证明是先从XML配置创建Bean,把XML注释后发现name是“这是使用@Bean方式创建的”说明其次是从@Bean方式创建的