> 文档中心 > 【Spring从入门到实战教程】第四章 Spring 注解式开发详解

【Spring从入门到实战教程】第四章 Spring 注解式开发详解


四、Spring 注解式开发

    JavaWeb项目经历的阶段:
        1、Servlet + JSP / Servlet + 模板引擎,该阶段XML配置很少,但是需要从基础代码开始编写;
        2、SSH(Spring/Struts2/Hibernate) / SSM(Spring/SpringMVC/Mybatis),该阶段无需从基础代码开始编写,但是需要大量的XML配置;
        3、SSM,该阶段无需从基础代码开始编写,采用注解+XML配置;
        4、SpringBoot,零配置(XML),采用注解+yml配置;

4.1 概述

    从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。

    Spring是开发中必不可少的一个框架,基于传统的xml方式配置太过繁琐,Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。

4.1.1 Xml优缺点

优点:

  • 降低类与类之间的耦合,修改方便,容易扩展;

  • 容易和其他系统进行数据交互;

  • 对象之间的关系一目了然;

缺点:

  • 配置冗长,需要额外维护,影响开发效率;

  • 类型不安全,校验不出来,出错不好排查;

4.1.2 注解优缺点

优点:

  • 简化配置;

  • 使用起来直观且容易,提升开发的效率;

  • 类型安全,容易检测出问题;

缺点:

  • 修改起来比xml麻烦;

  • 如果不项目不了解,可能给开发和维护带来麻烦;

注解简单概括:写起来比较简单、方便,看起来也简洁,但是修改麻烦;

Xml配置概括:写起来比较灵活、修改方便,但是写和维护麻烦;

4.2 开启注解

4.2.1 组件扫描

    Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

  • 组件扫描(component scanning):Spring能够从classpath下自动扫描,侦测和实例化具有特定注解的组件,使Spring中的注解生效;

  • 对于扫描到的组件,Spring有默认的命名规则:使用首字母小写的类名,作为默认bean的名称,也可以在注解中通过value属性值标识组件的名称;

  • 在Spring的配置文件中声明标签,实现组件扫描;

  • 标签的base-package属性指定一个需要扫描的基础类包,Spring容器将会扫描这个基础类包里及其子包中的所有类;

  • 当需要扫描多个包时,可以使用逗号分隔;或配置多个标签;

注意:在使用 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束。

4.2.2 扫描过滤

  • 4.2.2.1 粗粒度过滤

如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类:

  • 4.2.2.2 细粒度的过滤

可采用子标签表示要包含的目标类和表示要排除在外的目标类。其中type属性有以下5种(主要使用前两种):

  1. annotation:过滤器扫描使用注解所标注的那些类,通过expression属性指定要扫描的注释;

  2. assignable:过滤器扫描派生于expression属性所指定类型的那些类;

  3. aspectj:过滤器扫描与expression属性所指定的AspectJ表达式所匹配的那些类;

  4. regex:过滤器扫描类的名称与expression属性所指定正则表示式所匹配的那些类;

  5. custom:使用自定义的org.springframework.core.type.TypeFliter实现类,该类由expression属性指定

注意:若使用去过滤扫描内容,要在use-default-filters="false"的情况下,不然会失效,被默认的过滤机制所覆盖。在use-default-filters="false"的情况下,exclude-filter是针对include-filter里的内容进行排除。

不包含:

@Componentpublic class Dog {    @Override    public String toString() { return "Dog";    }}//演示派生排除时添加继承关系@Componentpublic class SmallDog {    @Override    public String toString() { return "SmallDog";    }}
        

自定义注解实现不包含:

public @interface MyFilter {}
        

仅包含:

    

4.3 配置Bean

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解 说明
@Component 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller 该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

公有属性value:表示Bean的名称。

实体类:

@Componentpublic class Person {    private int id;    private String name;    private double money;    public int getId() { return id;    }    public void setId(int id) { this.id = id;    }    public String getName() { return name;    }    public void setName(String name) { this.name = name;    }    public double getMoney() { return money;    }    public void setMoney(double money) { this.money = money;    }    @Override    public String toString() { return "Person{" +  "id=" + id +  ", name='" + name + '\'' +  ", money=" + money +  '}';    }}

Dao接口及其实现类:

public interface PersonDao {    void insertPerson();}@Repository("personDao")public class PersonDaoImpl implements PersonDao {    @Override    public void insertPerson() { System.out.println("PersonDaoImpl insertPerson()执行了...");    }}

Service接口及其实现类:

public interface PersonService {    void insertPerson();}@Service("personService")public class PersonServiceImpl implements PersonService {    @Override    public void insertPerson() { System.out.println("PersonServiceImpl insertPerson()执行了...");    }}

控制层代码:

@Controllerpublic class PersonController {    public void insertPerson(){ System.out.println("PersonController insertPerson()执行了...");    }}

测试:

public class AnnotationTest {    @Test    public void testBeanAnnotation(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); try {     Dog dog = ac.getBean("dog", Dog.class);     System.out.println(dog); }catch (Exception e){     System.out.println(e.getMessage()); } System.out.println("------------------------------------"); try {     SmallDog smallDog = ac.getBean("smallDog" ,SmallDog.class);     System.out.println(smallDog); }catch (Exception e){     System.out.println(e.getMessage()); } System.out.println("------------------------------------"); try {     Person p1 = ac.getBean("person", Person.class);     System.out.println(p1); }catch (Exception e){     System.out.println(e.getMessage()); } System.out.println("------------------------------------"); try {     PersonDao personDao = ac.getBean("personDao", PersonDao.class);     personDao.insertPerson(); }catch (Exception e){     System.out.println(e.getMessage()); } System.out.println("------------------------------------"); try {     PersonService personService = ac.getBean("personService", PersonService.class);     personService.insertPerson(); }catch (Exception e){     System.out.println(e.getMessage()); } System.out.println("------------------------------------"); try {     PersonController personController = ac.getBean("personController", PersonController.class);     personController.insertPerson(); }catch (Exception e){     System.out.println(e.getMessage()); }    }}

4.4 组件装配

我们可以通过以下注解将定义好 Bean 装配到其它的 Bean 中。

@Value:为组件属性注入字面值。

public class Person {    /*     * 简单类型的依赖注入     * @Value     * 作用:依赖注入,注入字面值     * 位置:成员变量,方法(setter方法)     * 我们一般情况下都是写在成员变量上面:     *1、成员变量位于类内部的顶部,方便查找      *2、后期使用Lombok插件,没有getter和setter方法     * 扩展使用:结合Spring表达式加载properties中的数据     */    @Value("10001")    private int id;    @Value("张三")    private String name;    @Value("3000.8")    private double money;    //省略}

@Autowired和@Resource:自动装配注解,即自动注入。

@Autowired:可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。

  • 构造方法,普通属性(即使是非public),一切具有参数的方法都可以使用@Authwired注解;

  • 所有使用@Autowired注解的属性都要求依赖的bean对象必须存在。当Spring找不到匹配的bean装配属性时,会抛出异常。若该属性允许为null值,可以设置@Authwired注解的required属性为false;

  • 当IOC容器里存在多个类型兼容的bean对象时,通过类型的自动装配将无法工作。此时可以在@Qualifier注解里提供bean的名称,Spring会通过名称自动装配;

  • @Authwired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的bean进行自动装配;

  • @Authwired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的bean;

  • @Authwired注解用在java.util.Map上时,若该Map的键值为String,那么Spring将自动装配与之Map值类型兼容的bean,此时bean的名称作为键值;

  • 旧版本的IDEA,如果接口没有实现类,使用@Authwired注解会报红,但是不影响使用。新版本IDEA不影响。

@Qualifier:与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

@Service("personService")public class PersonServiceImpl implements PersonService {    /*     * service->dao传统写法:     *  在service中创建dao对象,然后调用指定的方法     */    //private PersonDao personDao = new PersonDaoImpl();    /*     * service->dao XML配置写法:     *      *       */    /*private PersonDao personDao;    public void setPersonDao(PersonDao personDao) { this.personDao = personDao;    }*/    /*     * service->dao @Autowired注解写法:     * 实现自定义类型的依赖注入,无需setter方法,自动装配     *     * 位置:目前主要使用在成员变量     * 配置:required默认值为true,表示当前依赖注入的对象必须在IOC容器中存在,否则抛出异常     *     * @Autowired注解:     *  默认按照 Bean 的类型进行装配     *  如果存在多个该类型bean存在,那么自动切换到按名称匹配     *  如果存在多个该类型bean存在,并且多个对象的bean名称与名称匹配失败,则抛出异常     *      解决方法:@Qualifier 指定bean名称     */    //@Autowired    @Autowired(required = false)    @Qualifier("personDaoImpl")    private PersonDao personDao;    @Override    public void insertPerson() { System.out.println("PersonServiceImpl insertPerson()执行了..."); if (personDao != null) {     personDao.insertPerson(); }    }}
@Testpublic void testAutowired(){    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");    try { PersonService personService = ac.getBean("personService", PersonService.class); personService.insertPerson();    }catch (Exception e){ System.out.println(e.getMessage());    }}

@Resource:作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。@Resource 中有两个重要属性:name 和 type。

  • @Resource注解要求提供一个bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为bean的名称;

  • Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。

  • 如果指定 name 属性,则按实例名称进行装配;

  • 如果指定 type 属性,则按 Bean 类型进行装配;

  • 如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。

@Controllerpublic class PersonController {    @Resource    private PersonService personService;    public void insertPerson(){ System.out.println("PersonController insertPerson()执行了..."); personService.insertPerson();    }}

4.5 Java Config

    JavaConfig,是在 Spring 3.0 开始从一个独立的项目并入到 Spring 中的。JavaConfig 可以看成一个用于完成 Bean 装配的 Spring 配置文件,即 Spring 容器,只不过该容器不是 XML文件,而是由程序员使用 Java 自己编写的 Java 类。

Dept.java:

public class Dept {    private int deptno;    private String dname;    public int getDeptno() { return deptno;    }    public void setDeptno(int deptno) { this.deptno = deptno;    }    public String getDname() { return dname;    }    public void setDname(String dname) { this.dname = dname;    }    @Override    public String toString() { return "Dept{" +  "deptno=" + deptno +  ", dname='" + dname + '\'' +  '}';    }}

Emp.java:

public class Emp {    private int empno;    private String ename;    private Dept dept;    public int getEmpno() { return empno;    }    public void setEmpno(int empno) { this.empno = empno;    }    public String getEname() { return ename;    }    public void setEname(String ename) { this.ename = ename;    }    public Dept getDept() { return dept;    }    public void setDept(Dept dept) { this.dept = dept;    }    @Override    public String toString() { return "Emp{" +  "empno=" + empno +  ", ename='" + ename + '\'' +  ", dept=" + dept +  '}';    }}

配置类:

    定义 JavaConfig 类,在类上使用@Configuration 注解,将会使当前类作为一个 Spring 的容器来使用,用于完成 Bean 的创建。在该 JavaConfig 的方法上使用@Bean,将会使一个普通方法所返回的结果变为指定名称的 Bean 实例。

/* * 通过@Configuration注解,让该类成为Spring的配置类 * * @Configuration注解 *  作用:表示当前类为配置类,类似于applicationContext.xml */@Configurationpublic class MyConfig {    /*     * 配置bean:主要用于第三方jar中的类,自定义类一般使用@Component、@Repository、@Service、@Controller     * 实现方式:自定义方法+@Bean注解     * 自定义方法的返回值:Bean的类型     * 自定义方法的名称:Bean的名称     *     * @Bean注解:     *  name/value属性:配置bean的名称     *  autowire:自动注入的方式     */    @Bean("dept")    public Dept getDept() { Dept dept = new Dept(); dept.setDeptno(10); dept.setDname("研发部"); return dept;    }    //该注解表示:将一个叫做emp的对象放入IOC容器中    //并且通过byType的方式注入dept    @Bean(name = "emp", autowire = Autowire.BY_TYPE)    public Emp getEmp() { Emp emp = new Emp(); emp.setEmpno(8001); emp.setEname("张三"); return emp;    }}

测试:

public class JavaConfigTest {    @Test    public void test(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Dept dept = ac.getBean("dept", Dept.class); System.out.println(dept); Emp emp = ac.getBean("emp", Emp.class); System.out.println(emp);    }}

4.6 AOP的实现

4.6.1 开启AOP注解

4.6.2 AOP中使用的注解

  • @Aspect:配置切面类

  • @Before:配置前置通知

  • @After:配置后置通知

  • @AfterReturning:配置返回通知

  • @AfterThrowing:配置异常通知

  • @Order:配置切面优先级

  • @Pointcut:配置切点表达式

@Component@Aspect@Order(2)public class LogAspect {    @Before("execution(* com.newcapec.service.impl.*.*(..))")    public void beforeMethod() { System.out.println("AOP日志记录:前置通知......");    }    //公共切点表达式    @Pointcut("execution(* com.newcapec.service.impl.*.*(..))")    public void exp() {    }    @After("exp()")    public void afterMethod() { System.out.println("AOP日志记录:后置通知......");    }    @AfterReturning(value = "exp()", returning = "result")    public void afterReturnMethod(Object result) { System.out.println("AOP日志记录:返回通知......" + result);    }    @AfterThrowing(value = "exp()", throwing = "ex")    public void afterThrowMethod(Exception ex) { System.out.println("AOP日志记录:异常通知......" + ex);    }}
@Component@Aspect@Order(1)public class OtherAspect {    @Before("execution(* com.newcapec.service.impl.*.*(..))")    public void beforeM(){ System.out.println("OtherAspect的beforeM方法.....");    }}

4.7 事务管理

4.7.1 启用事务注解

        

4.7.2 事务注解

在事务方法上加注解@Transactional:

@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED,  readOnly = true,timeout = 20,rollbackFor = {ClassNotFoundException.class})public void transfer(int fromId, int toId, double money) throws Exception {}