四、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 扫描过滤
如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类:
<!-- 粗粒度过滤 -->
<context:component-scan base-package="com.newcapec" resource-pattern="bean/*.class"/>
可采用子标签<context:include-filter> 表示要包含的目标类和<context:exclude-filter> 表示要排除在外的目标类。其中type属性有以下5种(主要使用前两种):
-
annotation:过滤器扫描使用注解所标注的那些类,通过expression属性指定要扫描的注释; -
assignable:过滤器扫描派生于expression属性所指定类型的那些类; -
aspectj:过滤器扫描与expression属性所指定的AspectJ表达式所匹配的那些类; -
regex:过滤器扫描类的名称与expression属性所指定正则表示式所匹配的那些类; -
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 {
}
|