1、基础的基础概念
是指在类上或者方法上加上@XX的,完成特定的功能
2、注解的作用
- 替代xml配置,简化配置
从配置文件的方式,转化为注解的方式创建对象
下面的xml的创建bean方式等价于上面的注解创建方式
3、Spring注解开发的问题
我们使用配置文件,是为了解耦。现在,又将注解写入到了代码中,那到底是增加了耦合还是解耦了呢?
注解只是替代了配置文件,让我们更加方便的创建对象,本质上还是解耦
4、Spring的基础注解
我们要想使用注解,首先应该告诉Spring,我们的注解在哪个包下,他需要扫描哪个包路径。这样我们的写的注解才可以生效
<context:component-scan base-package="com.wx.annotation"></context:component-scan>
4.1、Spring创建对象的注解
4.1.1、@Component注解
@Component注解可以替代我们配置文件中的<bean>标签,class就是配置注解类的全限定类名,id就是首字母小写,如User类就是user
实体类
@Component public class User implements Serializable { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.wx.annotation"></context:component-scan> </beans>
测试类
public class UserTest { @Test public void testAnnoBean(){ ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); User user = (User) ctx.getBean("user"); System.out.println(user); } }
测试结果
注解@Component也可以指定id,如@Component(value = "user2"),那么getBean("user2")就需要通过这个id来获取
如果对配置不满意,我们也可以通过配置文件来覆盖我们的注解配置,但是id必须要保持一致
配置文件
<bean id="user" class="com.wx.annotation.entiy.User"></bean>
测试日志输出的结果
2021-05-09 20:06:02 DEBUG DefaultListableBeanFactory:906 - Overriding bean definition for bean 'user' with a different definition: replacing [Generic bean: class [com.wx.annotation.entiy.User]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\IdeaProjects\myspring-annotation\target\classes\com\wx\annotation\entiy\User.class]] with [Generic bean: class [com.wx.annotation.entiy.User]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [applicationContext.xml]]
通过日志输出,我们可以看到,配置文件中的配置覆盖了我们的注解配置
注意,我们在这么做的时候,一定要注意配置文件中的id是否写的正确,如果不正确,spring会帮我们创建两个bean对象出来,这样在注入的时候,可以会报冲突
4.1.2、@Service,@Controller,@Repository注解
这三个注解都是@Component的衍生注解,通过源码可以发现,他们也是使用的@Component注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { @AliasFor( annotation = Component.class ) String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { @AliasFor( annotation = Component.class ) String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository { @AliasFor( annotation = Component.class ) String value() default ""; }
那为什么还需要这三个注解呢?这是因为方便我们区分这些注解的类的作用
@Repository,我们建议用在Dao层
@Controller,建议用在控制层Controller
@Service,建议用在业务层Service上,这些都是Spring建议给我们的。
由于我们的dao层都是使用动态代理生成的,所以@Repository这个注解几乎不会使用
4.1.3、@Scope注解
用来设置创建对象的次数,等同于<bean scope="singleton">配置
这个是控制spring帮助我们创建一次,还是每次调用getBean都生成一个新的对象
实体类
@Component @Scope("singleton") public class User implements Serializable { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
测试类
Spring默认bean对象就是单例的,我们可以通过@Scope("prototype")注解将对象设置为多例
4.1.4、@Lazy注解
延迟创建单例对象
我们知道,单例对象使在对象功能创建的时候创建好的,如果需要修改到getBean的时候才创建,要在配置文件<bean>中添加lazy-init="true"这个配置。而@Lazy就是代替这个配置的注解
<bean id="user" class="com.wx.annotation.entiy.User" scope="prototype" lazy-init="true"></bean>
@Lazy注解出现后,默认值是true,只需要加上这个注解,就会在getBean的时候才去创建对象
实体类
测试
4.2、声明周期相关注解
spring中bean对象生命周期中,有两个比较重要的方法,一个是初始化相关的方法,一个是销毁相关的方法
对于初始化方法,我们需要先实现InitializingBean接口,并在配置文件<bean>中添加配置init-method="myInit"
对于销毁方法,也需要实现DisposableBean接口,并在配置文件<bean>中添加配置destroy-method="myDestory"
使用注解后,我们可以通过注解来代替这些操作
- @PostConstruct--------初始化
- @PreDestroy----销毁
实体类
@Component @Scope("singleton") @Lazy public class User implements Serializable { private String name; private Integer age; @PostConstruct public void myInit(){ System.out.println("User.myinit"); } @PreDestroy public void myDestroy(){ System.out.println("User.myDestroy"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
测试类
实现了通过注解在对象初始化和销毁的时候,做出了信息输出
4.3、注入相关的注解
上面将的都是对象的创建,现在来说一下对象的注入
对象的注入有两种方式,一种是用户自定义注解(@Autowired),一种是JDK类型的注入
使用配置文件注入的时候,需要先将两个对象<bean>创建出来,然后在进行set注入。现在,我们让spring来帮我们进行注入,是不是只需要将@Autowired加在我们的set方法上即可呢?
4.3.1、@Autowired注解
service实现
public interface UserService { public void save(); } @Service public class UserServiceImpl implements UserService { private UserDao userDao; @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void save() { userDao.save(); } }
dao层实现
public interface UserDao { void save(); } @Repository public class UserDaoImpl implements UserDao { public void save() { System.out.println("UserDaoImpl.save"); } }
测试类
我们在set方法上添加了注解,spring会帮助我们进行注入,本质也是set注入
@Autowired注入是通过类型进行注入,必须和成员变量相同的类型,或者是子类、接口的实现类
@Autowired注入也可以通过名字di进行注入,但是需要配合一个注解@Qualifier一起使用
@Autowired不仅可以放在set方法上,也可以放在成员变量上。放在set方法上,是直接调用set方法进行注入,放在成员变量上,是通过反射进行注入。如果set方法和成员变量都加了@Autowired注解,优先走set方法。
4.3.2、@Resource注解
可以通过@Autowired、@Qualifier这两个注解配合使用,达到按照名称id注入的目的。因为我们注入id默认是首字母小写,所以这里@Qualifier的值填写userDaoImpl
@Service public class UserServiceImpl implements UserService { private UserDao userDao; @Autowired @Qualifier("userDaoImpl") public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void save() { userDao.save(); } } @Repository public class UserDaoImpl implements UserDao { public void save() { System.out.println("UserDaoImpl.save"); } }
推荐使用@Autowired按照类型注入
@Autowired、@Qualifier("userDaoImpl")这两个注解的效果等价于@Resource(name = "userDaoImpl")这个注解
//这两段代码等价 @Autowired @Qualifier("userDaoImpl") public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Resource(name = "userDaoImpl") public void setUserDao(UserDao userDao) { this.userDao = userDao; }
如果@Resource注解没有指定name值或者没有找到指定的name,那么会自动转换为根据类型注入
@Service public class UserServiceImpl implements UserService { private UserDao userDao; @Resource public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void save() { userDao.save(); } }
4.4、JDK相关的注解
上面说到,我们的配置文件中的<bean>标签被我们的@Component注解进行替代了,帮助我们创建了bean对象,又通过@Autowired帮助我们注入了复杂类对象。那么对象中的JDK类型的属性如何进行注入呢?
这个时候我们可以创建一个xxx.properties配置文件,通过key,value的形式来进行赋值,那么Spring如何来读取这个配置文件呢?
实体类
public class User implements Serializable { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
可以通过这个配置将自定义配置文件读取
<context:property-placeholder location="classpath:xxx.properties"></context:property-placeholder>
现在spring就可以读取到我们的配置文件了,还需要通过注解的方式,将配置文件中的值注入到属性中
Spring为我们提供了@Value注解,来进行注入
public class User implements Serializable { @Value("${name}") private String name; @Value("${age}") private Integer age; //省略getset方法 }
配置文件application.properties
name=zhangsan age=18
测试
其实我们还是在依赖配置文件,用来加载自定义的配置文件,那么Spring有为我们提供注解来解决这个问题吗?@PropertySource()注解就是帮助我们加载自定义配置文件的
@PropertySource("classpath:/application.properties") public class User implements Serializable { @Value("${name}") private String name; @Value("${age}") private Integer age; //省略getset方法 }
注意
@Vlaue不能作用于静态变量
@Vlaue不能注入集合类型
4.5、扫描类注解
我们一直是通过一个配置来告诉Spring需要扫描的包,让Spring帮助我们扫描
<context:component-scan base-package="com.wx.annotation"></context:component-scan>
Spring会根据这个路径,扫描该目录及子目录下的所有注解。帮助我们进行注入,那如果我们只希望Spring只扫描这个包下指定的一些包该咋办呢?
4.5.1、排除方式
我们配置文件中有一个exclude-filter标签,帮助我们排除指定的一些包。type值有5种
- annotation:排除特定的注解,不扫描
- aspectj:通过切入点表达式进行过滤
- assignable:排除特定的类,不扫描
- custom:自定义排除
- regex:正则表达式
排除指定的类,不进行扫描
<context:component-scan base-package="com.wx.annotation"> <context:exclude-filter type="assignable" expression="com.wx.annotation.entiy.User"/> </context:component-scan>
排除指定的类,和指定的注解,不进行扫描
<context:component-scan base-package="com.wx.annotation"> <context:exclude-filter type="assignable" expression="com.wx.annotation.entiy.User"/> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/> </context:component-scan>
4.5.2、包含方式
包含方式决定了spring要扫描哪些注解
use-default-filters="false",spring默认是扫描包下所有的注解,所以需要加上这个配置,不适用默认策略,扫描所有
<context:component-scan base-package="com.wx.annotation" use-default-filters="false"> <context:include-filter type="assignable" expression="com.wx.annotation.entiy.User"/> </context:component-scan>
这个和上面那个相反
注解只能作用于程序员开发的类上,如SqlsessionFactoryBean这种复杂对象,我们还是需要使用配置文件进行注入。