【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种(主要使用前两种):
-
annotation:过滤器扫描使用注解所标注的那些类,通过expression属性指定要扫描的注释;
-
assignable:过滤器扫描派生于expression属性所指定类型的那些类;
-
aspectj:过滤器扫描与expression属性所指定的AspectJ表达式所匹配的那些类;
-
regex:过滤器扫描类的名称与expression属性所指定正则表示式所匹配的那些类;
-
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 {}