Spring注解编程

1.为什么要讲解注解编程?

  • 注解开发方便 :代码简洁,开发速度大大提高
  • Spring的开发潮流:Spring2.X引入注解,Spring3.X完善注解,Spring Boot普及注解编程

2.注解的作用:

  • 替换XML这种配置形式,简化配置

如开发一个User类:

public class user{....}

添加注解@Component后如下:

@Component
public class user{....}

该形式等价于在spring配置文件中的:

<bean id="user" class="com.example.entity.User"/>

由此,我们可以看出注解确实能够极大地简化我们的配置内容。

  • 替换接口,实现调用双方的契约性

如定义一个调用者Invoker:

public class Invoker{
    public void invoke(){
        目标:调用Provider类中的m1方法.....
    }
}

定义一个提供者Provider继承某个接口,实现其3个方法:

public class Provider implements XXXXX{
    public void m1(){}
    public void m2(){}
    public void m3(){}
}

​ 但是问题来了,我们仅需要调用m1方法,但是Provider必须将三个方法全都实现,这样就有些麻烦了。

​ 而通过注解的契约性,我们就可以很好的解决这个问题了。

public class Provider implements XXXXX{
    //注解名是随便写的
    @XXX
    public void m1(){}
    public void m2(){}
    public void m3(){}
}

​ 我们在想要调用的方法上标明一个注解(名字的话可以自己定义),然后调用者(Invoker)通过反射扫描注解获得方法信息。即:通过注解的方式,在功能调用者和功能提供者之间达成约定,进而进行功能的调用

以切面编程举例:
在这里插入图片描述

3.对象创建注解:

首先要先在Spring配置文件中添加一个标签:

<context:component-scan base-package="com.example"/>

目的是让Spring框架在上面设置的包及其子包中扫描对应的注解,使其生效。

  • @Component

    上面已经给出示例,可以相当于在Spring的配置文件中配置了一个bean。

    @Component注解有三种衍生注解(本质上来讲,三个注解几乎可以说都等同于@Component,但是Spring主要是为了区分它们的作用,才将@Component划分成了三注解):

    • @Repository

      主要用于DAO层(持久层)上,如UserDAO接口实现类在Spring配置文件中的声明:

      <bean id="userDAO" class="com.example.dao.UserDAOImpl"/>
      

      等价于:

      @Repository
      public class UserDAOImpl implements UserDAO {
          ......
      }
      
    • @Service

      主要用于业务层上,如UserService接口实现类在Spring配置文件中的声明“

      <bean id="userService" class="com.example.service.UserServiceImpl"/>
      

      等价于:

      @Service
      public class UserServiceImpl implements UserService{
      	......
      }
      
    • @Controller

      主要用在控制层上,如UserController在Spring配置文件中的声明:

      <bean id="userController" class="com.example.controller.UserController"/>
      

      等价于:

      @Controller
      public class UserController {
          ......
      }
      

​ 注:我们需要通过id名来获取对象,id名即类名首单词首字母小写。当然,命名我们也可以自己定义,如@Service(“userService”),就将原本bean的id通过注解应该为userServiceImpl修改为了userService。
在这里插入图片描述

  • @Scope

    作用:控制简单对象创建次数。

    我们在声明bean的时候,也会在后面添加scope配置,如下:

    <bean id="user" class="com.example.entity.User" scope="prototype"/>
    

    scope包括四种属性:

    1、singleton(单例):一个Spring容器全局中只有一个bean的实例,此为Spring的默认配置
    2、prototype(原型):每次调用都会新建一个bean的实例
    3、Request(请求):Web项目中,针对每一个http请求都会创建一个新的bean,同时该bean仅在HTTP request内有效
    4、Session(会话):Web项目中,针对每一个http请求都会创建一个新的bean,同时该bean仅在HTTP session内有效

    使用如下:

    @Scope("prototype")
    public class User {
    	......
    }
    
  • @Lazy

    作用:延迟创建单实例对象。

    我们在声明bean的时候,也会在后面添加lazy-init配置,如下:

    <bean id="user" class="com.example.entity.User" lazy-init="false">
    
    • true:在使用getBean()方法创建对象(即使用这个对象的时候)的时候将我们的实例对象创建出来
    • false(默认):Spring工厂创建时就会将我们的实例对象创建出来
    @Lazy
    public class User {
        ......
    }
    

    只要加上这个注解,就代表需要进行懒加载(即true),不写就是默认值(false)。

4.生命周期相关注解:

  • 初始化相关的方法----》@PostConstruct
    原来的写法:

    方法1.先给出我们要创建实例的类,在类中写一个用来初始化的方法,然后在bean标签内写一个init-method的属性

    public class InitTest{
       	public void initMethod(){
            System.out.println("InitTest.initMethod");
        }
    }
    
    <bean id="init" class="com.example.InitTest" init-method="initMethod"/>
    

    方法2.对应的类实现InitializingBean接口,然后Spring配置文件中声明即可自动补充init-method为我们重载的方法:

    import org.springframework.beans.factory.InitializingBean;
    
    public class InitTest implements InitializingBean {
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("InitTest.afterPropertiesSet");
        }
    }
    
    <bean id="init" class="com.example.InitTest"/>
    
  • 销毁相关的方法----》@PreDestroy
    原来的写法:

    方法1.先给出我们要创建实例的类,在类中写一个用来销毁的方法,然后在bean标签内写一个destroy-method的属性

    public class DestroyTest implements DisposableBean {
        public void destroyMethod() {
            System.out.println("DestroyTest.destroyMethod");
        }
    }
    
    <bean id="destroy" class="com.baizhi.DestroyTest" destroy-method="destroyMethod"/>
    

    方法2.对应的类实现DisposableBean接口,然后bean标签内写一个destroy-method的属性:

    import org.springframework.beans.factory.DisposableBean;
    
    public class DestroyTest implements DisposableBean {
        @Override
        public void destroy() throws Exception {
            System.out.println("DestroyTest.destroy");
        }
    }
    
    <bean id="destroy" class="com.baizhi.DestroyTest"/>
    
在以上两注解应用之后,Spring将会自动调用:
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class Product {

    @PostConstruct
    public void myInit(){}

    @PreDestroy
    public void destroy(){}
}

5.与注入相关的注解:

主要分为两种情况:
  • 1.用户自定义类型的注入,我们只需要使用@Autowired

这里先回忆一下,我们使用UserService的实现类需要UserDAO,那我们就需要在UserService实现类中声明UserDAO对象,代码如下:

UserDAO的实现类UserDAOImpl.java:
public class UserDAOImpl implements UserDAO{ 
    @Override
    ......
}
UserService的实现类UserServiceImpl.java:
public class UserServiceImpl implements UserService{
    private UserDAO userDAO;
    //set方法
    public void setUserDAO(UserDAO userDAO){this.userDAO = userDAO;}
    //get方法
    getUserDAO ......
        
    @Override
    ......
}

我们原本的配置方法是这样的:

<bean id="userDAO" class="com.example.dao.UserDAOImpl"/>
<bean id="userService" class="com.example.service.UserServiceImpl">
	<property name="userDAO" ref="userDAO"/>
</bean>

那么现在,我们将使用注解进行和上面代码一样的操作:

接口实现类UserDAOImpl.java:
@Repository
public class UserDAOImpl implements UserDAO {
    ......
}
接口实现类UserServiceImpl.java:
@Service
public class UserServiceImpl implements UserService{
	private UserDAO userDAO;
    //set方法,这样UserDAO对象就自动的被Spring注入给了UserService
    @Autowired
    public void setUserDAO(UserDAO userDAO){this.userDAO = userDAO;}
    //get方法
    getUserDAO ......
        
    @Override
    ......
}

分析:在进行依赖注入的时候,工厂将调用相应属性的set方法进行注入,所以Autowired注解相应的需要书写在set方法上。

完整代码:

UserDAO.java:

public interface UserDAO {
    public void save();
}

UserDAOImpl.java:

@Repository
public class UserDAOImpl implements UserDAO {
    @Override
    public void save() {
        System.out.println("UserDAOImpl.save");
    }
}

UserService.java:

public interface UserService {
    public void register();
}

UserServiceImpl.java:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
//这里使用了自定义id
@Service("userService")
public class UserServiceImpl implements UserService {
    private UserDAO userDAO;

    public UserDAO getUserDAO() {
        return userDAO;
    }

    //注入
    @Autowired
    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    @Override
    public void register() {
        userDAO.save();
        System.out.println("UserServiceImpl.register");
    }
}

测试类:

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InjectionTest {

    @Test
    public void test(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) ctx.getBean("userService");
        userService.register();
    }
}

输出结果:
在这里插入图片描述

@Autowired注解使用需注意:
  • 如果我们现在有一个UserDAOImpl和一个ProductDAOImpl,都添加了@Repository注解,我们之后进行Autowired注入的时候为什么能做到准确注入UserDAOImpl呢?

    我们应该清楚一点:Autowired注解是基于类型进行注入的,就是说我们需要注入的类型,必须是与成员变量相同的类型或者是子类再或者是接口的实现类(如UserServiceImpl类中注入的是UserDAO接口的实现类UserDAOImpl)

  • 上面一条提到Autowired是基于类型进行注入的,但是此时应用需要基于名字进行注入的话,Autowired就显得乏力了。我们可以引入一个新的注解@Qualifier,这样就可以基于名字进行注入了:

    @Qualifier("userDAOImpl")
    @Autowired
    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }
    

    虽说是基于名字,但实际上比较的是bean的id值,如果将UserDAOmpl的@Repository注解进行id另起的话,也是无法匹配的。

  • @Autowired注解的放置位置:我们将@Autowired注解放在set方法上,那么在创建对象的时候,就会调用set方法为对应的成员变量赋值,这样也是清晰明了。但是,我们也可以直接将@Autowired注解直接放在成员变量上,这样的注入也是成功的,但这样的方式将不会调用set方法,Spring将通过反射完成上述操作。(推荐该方法,可以使我们的代码更加的简洁)

    @Autowired
    private UserDAO userDAO;
    
    /*
    可省略
    public UserDAO getUserDAO() {
        return userDAO;
    }
    
    public void setUserDAO(UserDAO userDAO) {
        System.out.println("UserServiceImpl.setUserDAO");
        this.userDAO = userDAO;
    }
    */
    
  • JavaEE规范中相似功能的注解:

    • JSR 250 @Resource注解,是依据名字进行注入的,在没有给出name属性的时候,可以基于类型进行注入,等价于@Autowired和@Qualifier的整合,直接书写@Resource(name = “UserDAOImpl”)即可
    public class UserServiceImpl implements UserService {
    
        @Resource(name = "userDAOImpl")
        private UserDAO userDAO;
        ......
    }
    

    运行结果都是一样的。

    注意:如果在应用Resource注解的过程中,名字没有配对成功,那么它会继续按照类型进行注入。
    • JSR 330 @Inject注解,作用和@Autowired完全一致,基于类型进行注入,需要导包(具体就不写了,了解即可):
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
    
  • 2.JDK类型成员变量的注入

这里先回忆一下,我们定义一个User类,然后要将它在Spring配置文件中进行声明并为对应参数进行赋值,具体代码如下:

User.java:

public class User{
    private Integer id;
    private String name;
    .
    .
    set和get方法等......
}

ApplicationContext.xml:

...
<bean id="user" class="com.example.entity.User">
	<property name="id" value="1"/>
    <property name="name" value="张三"/>
</bean>
...

可是,我们如果使用了注解开发,就不会在Spring配置文件里书写bean标签了,我们应该如何为参数进行赋值呢?

1.将属性放在xxx.properties文件中,利用键值对的方式进行赋值操作,之后使用@Value注解进行注入:

开发步骤:

首先需要在Spring配置中声明让Spring能够找到 .properties文件:

<context:property-placeholder location="classpath:application.properties"/>

其次在User.java类中使用@Value注解进行注入:

@Component
public class User{
    
    @Value("${id}")
    private Integer id;
    @Value("${name}")
    private String name;
    .
    .
    set和get方法等......
}

最后在 .properties文件(本例为application.properties)定义对应的值:

id = 1
name = 张三

测试类如下:

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ValueTest {

    @Test
    public void test(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User) ctx.getBean("user");
        System.out.println(user.toString());
    }
}

输出结果(注意,若输出乱码,请在设置中修改文件默认编码格式为utf-8):
在这里插入图片描述

2.@PropertySource注解,使配置趋于注解化

作用:用于替换在Spring配置文件中的**<context:property-placeholder location=" "/>**标签.

开发步骤:

首先依旧是需要 .properties文件:

id = 1
name = 张三

然后应用@PropertySource注解,相当于是将原本书写的location参数放到注解当中去了:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:application.properties")
public class User {

    @Value("${id}")
    private Integer id;

    @Value("${name}")
    private String name;
	......
    set和get等方法......
}

最后测试:

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ValueTest {

    @Test
    public void test(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User) ctx.getBean("user");
        System.out.println(user.toString());
    }
}

输出结果:
在这里插入图片描述

@Value注解的使用注意:
  • @Value注解下的成员变量不可以是静态的(即加了static关键字),如果使用,则注入失败,会使用创建对象的默认值

  • 在使用@Value+properties这种方式,是不可以注入集合类型的。

    那我们应该如何注入集合类型呢?

    ​ Spring为我们提供了一种新的文件形式YAML文件,后缀名xxx.yml或者xxx.yaml:

    #使用int的list
    student:
      ids: [1, 2, 3, 4, 5]
      
    #或者
    stu:
      - 1
      - 2
      - 3
      - 4
      - 5
    
3.此外,直接在@Value中写参数也是可以的(推荐):

设置包扫描:

<context:component-scan base-package="com.example"/>

User.java:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
public class User {

    @Value("1")
    private Integer id;

    @Value("李四")
    private String name;
    ......
}

测试类不变,最终输出:
在这里插入图片描述



最后做一些补充:
①注解扫描:

我们应该也清楚,要扫描注解需要进行**<context:component-scan base-package=" "/>的配置,在我们输入包的时候,Spring将会基于我们所提供的包以及其子包**进行扫描。

但是这样显得很不灵活。

比如我们在com.example包中,有些注解我们不想要去扫描了,我们该怎么去做呢?

  • 排除方式

    排除某些类上的注解,不进行扫描,进而不创建对象

    <context:component-scan base-package="com.example">
    	<context:exclude-filter type="assignable|annotation|aspectj|regex|custom" expression=""/>
    </context:component-scan>
    
    • assignable:排除特定的类型,不进行扫描

      <context:component-scan base-package="com.example">
      	<context:exclude-filter type="assignable" expression="com.example.entity.User"/>
      </context:component-scan>
      
    • annotation:排除特定的注解,不进行扫描

      <!--排除当前包及其子包中所有带有@Service注解的对象-->
      <context:component-scan base-package="com.example">
      	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
      </context:component-scan>
      
    • aspectj:依据切入点表达式,进行排除(★重要)

      <context:component-scan base-package="com.example">
          <!--排除bean包及其子包的注解,不进行扫描
      	切入点表达式:此处只支持两种:包切入点和类切入点,具体请自行搜索-->
      	<context:exclude-filter type="aspectj" expression="com.example.bean..*"/>
      </context:component-scan>
      
    • regex:使用正则表达式进行排除,和aspectj相似

    • custom:自定义排除策略,应用开发使用较少,略…

    注:这些排除方式是可以叠加的,如:

    <context:component-scan base-package="com.example">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    	<context:exclude-filter type="aspectj" expression="com.example.bean..*"/>
    </context:component-scan>
    
  • 包含方式

    <context:component-scan base-package="com.example" use-default-filters="false">
    	<context:include-filter expression="" type="assignable|annotation|aspectj|regex|custom"/>
    </context:component-scan>
    

    如果我们只要当前包及其子包下的某某类,用包含方式会更好。

    • use-default-filters:直接让Spring的默认注解扫描策略失效
    • <context:include-filter expression=" " type=" "/>:指定需要扫描的注解

    包含方式和排除方式没有什么太大区别,只是语义相反而已,也可以叠加,在此不谈了。

    ②对于注解开发的思考:
    • 配置互通

      作为Spring来讲,无论是作为xml的配置文件,还是注解配置,都是互通的。

      如下(非准确代码,理解含义即可):

      @Repository("userDAO")
      public class UserDAOImpl implements UserDAO{}
      
      
      public class UserService implements UserService{
          private UserDAO userDAO;
          ......
      }
      

      xml文件可以直接写:

      <bean id="userService" class="com.example.service.UserService">
      	<property name="userDAO" ref="userDAO"/>
      </bean>
      

      我们可以看到,在代码中注解@Repository下的UserDAOImpl(bean的id值自定义为userDAO)可以直接作为依赖项注入在配置文件中的bean中。

    • 什么情况下使用注解,什么情况下使用配置文件

      类是我写的,我才可以在自己的类上加@Component以及它的衍生注解。如果不是我们自己写的一些类,如在整合Mybatis的时候,我们会用到SqlSessionFactoryBean或者MapperScannerConfigure,这些类就目前来说(在Spring高级配置中解决了这些问题,但只说现在),我们需要在配置文件中配置。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值