【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 配置中,通过 <context:component-scan> 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

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

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

  • 在Spring的配置文件中声明<context:component-scan>标签,实现组件扫描;

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

  • 当需要扫描多个包时,可以使用逗号分隔;或配置多个<context:component-scan>标签;

<!-- 组件扫描 -->
<context:component-scan base-package="com.newcapec.bean"/>
<context:component-scan base-package="com.newcapec.dao"/>
<context:component-scan base-package="com.newcapec.service"/>
<context:component-scan base-package="com.newcapec.controller"/>

<!-- 上述写法可以简化为 -->
<context:component-scan base-package="com.newcapec"/>

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

4.2.2 扫描过滤

  • 4.2.2.1 粗粒度过滤

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

<!-- 粗粒度过滤 -->
<context:component-scan base-package="com.newcapec" resource-pattern="bean/*.class"/>
  • 4.2.2.2 细粒度的过滤

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

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

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

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

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

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

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

不包含:

@Component
public class Dog {
    @Override
    public String toString() {
        return "Dog";
    }
}

//演示派生排除时添加继承关系
@Component
public class SmallDog {
    @Override
    public String toString() {
        return "SmallDog";
    }
}
<!-- 细粒度过滤 -->
<!-- 默认扫描规则:base-package下所有的类,所有的子包,所有子包下的类都会被扫描 -->
<context:component-scan base-package="com.newcapec">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:exclude-filter type="assignable" expression="com.newcapec.bean.Dog"/>
</context:component-scan>

自定义注解实现不包含:

public @interface MyFilter {
}
<context:component-scan base-package="com.newcapec">
    <!--所有添加MyFilter注解的都排除在外-->
    <context:exclude-filter type="annotation" expression="com.newcapec.annotation.MyFilter"/>
</context:component-scan>

仅包含:

<context:component-scan base-package="com.newcapec" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

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的名称。

实体类:

@Component
public 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()执行了...");
    }
}

控制层代码:

@Controller
public 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配置写法:
     * <bean id="personDao" class="com.newcapec.dao.impl.PersonDaoImpl"/>
     *  <bean id="personService" class="com.newcapec.service.impl.PersonServiceImpl" p:personDao-ref="personDao"/>
     */
    /*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();
        }
    }
}
@Test
public 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 异常。

@Controller
public 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
 */
@Configuration
public 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注解

<!-- 开启AOP注解 -->
<aop:aspectj-autoproxy/>

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 启用事务注解

<!-- 1.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入数据源-->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 2.开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

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 {
}

猜你喜欢

转载自blog.csdn.net/ligonglanyuan/article/details/124787811