1.为什么要讲解注解编程?
- 注解开发方便 :代码简洁,开发速度大大提高
- Spring的开发潮流:Spring2.X引入注解,Spring3.X完善注解,Spring Boot普及注解编程
2.注解的作用:
如开发一个User类:
public class user{....}
添加注解@Component后如下:
@Component
public class user{....}
该形式等价于在spring配置文件中的:
<bean id="user" class="com.example.entity.User"/>
由此,我们可以看出注解确实能够极大地简化我们的配置内容。
如定义一个调用者Invoker:
public class Invoker{
public void invoke(){
目标:调用Provider类中的m1方法.....
}
}
定义一个提供者Provider继承某个接口,实现其3个方法:
public class Provider implements XXXXX{
public void m1(){}
public void m2(){}
public void m3(){}
}
但是问题来了,我们仅需要调用m1方法,但是Provider必须将三个方法全都实现,这样就有些麻烦了。
而通过注解的契约性,我们就可以很好的解决这个问题了。
public class Provider implements XXXXX{
//注解名是随便写的
@XXX
public void m1(){}
public void m2(){}
public void m3(){}
}
我们在想要调用的方法上标明一个注解(名字的话可以自己定义),然后调用者(Invoker)通过反射扫描注解获得方法信息。即:通过注解的方式,在功能调用者和功能提供者之间达成约定,进而进行功能的调用。
以切面编程举例:
3.对象创建注解:
首先要先在Spring配置文件中添加一个标签:
<context:component-scan base-package="com.example"/>
目的是让Spring框架在上面设置的包及其子包中扫描对应的注解,使其生效。
-
@Component
上面已经给出示例,可以相当于在Spring的配置文件中配置了一个bean。
@Component注解有三种衍生注解(本质上来讲,三个注解几乎可以说都等同于@Component,但是Spring主要是为了区分它们的作用,才将@Component划分成了三注解):
-
@Repository
主要用于DAO层(持久层)上,如UserDAO接口实现类在Spring配置文件中的声明:
<bean id="userDAO" class="com.example.dao.UserDAOImpl"/>
等价于:
@Repository public class UserDAOImpl implements UserDAO { ...... }
-
@Service
主要用于业务层上,如UserService接口实现类在Spring配置文件中的声明“
<bean id="userService" class="com.example.service.UserServiceImpl"/>
等价于:
@Service public class UserServiceImpl implements UserService{ ...... }
-
@Controller
主要用在控制层上,如UserController在Spring配置文件中的声明:
<bean id="userController" class="com.example.controller.UserController"/>
等价于:
@Controller public class UserController { ...... }
-
注:我们需要通过id名来获取对象,id名即类名首单词首字母小写。当然,命名我们也可以自己定义,如@Service(“userService”),就将原本bean的id通过注解应该为userServiceImpl修改为了userService。
-
@Scope
作用:控制简单对象创建次数。
我们在声明bean的时候,也会在后面添加scope配置,如下:
<bean id="user" class="com.example.entity.User" scope="prototype"/>
scope包括四种属性:
1、singleton(单例):一个Spring容器全局中只有一个bean的实例,此为Spring的默认配置
2、prototype(原型):每次调用都会新建一个bean的实例
3、Request(请求):Web项目中,针对每一个http请求都会创建一个新的bean,同时该bean仅在HTTP request内有效
4、Session(会话):Web项目中,针对每一个http请求都会创建一个新的bean,同时该bean仅在HTTP session内有效使用如下:
@Scope("prototype") public class User { ...... }
-
@Lazy
作用:延迟创建单实例对象。
我们在声明bean的时候,也会在后面添加lazy-init配置,如下:
<bean id="user" class="com.example.entity.User" lazy-init="false">
- true:在使用getBean()方法创建对象(即使用这个对象的时候)的时候将我们的实例对象创建出来
- false(默认):Spring工厂创建时就会将我们的实例对象创建出来
@Lazy public class User { ...... }
只要加上这个注解,就代表需要进行懒加载(即true),不写就是默认值(false)。
4.生命周期相关注解:
-
初始化相关的方法----》@PostConstruct
原来的写法:
方法1.先给出我们要创建实例的类,在类中写一个用来初始化的方法,然后在bean标签内写一个init-method的属性
public class InitTest{ public void initMethod(){ System.out.println("InitTest.initMethod"); } }
<bean id="init" class="com.example.InitTest" init-method="initMethod"/>
方法2.对应的类实现InitializingBean接口,然后Spring配置文件中声明即可自动补充init-method为我们重载的方法:
import org.springframework.beans.factory.InitializingBean; public class InitTest implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("InitTest.afterPropertiesSet"); } }
<bean id="init" class="com.example.InitTest"/>
-
销毁相关的方法----》@PreDestroy
原来的写法:
方法1.先给出我们要创建实例的类,在类中写一个用来销毁的方法,然后在bean标签内写一个destroy-method的属性
public class DestroyTest implements DisposableBean { public void destroyMethod() { System.out.println("DestroyTest.destroyMethod"); } }
<bean id="destroy" class="com.baizhi.DestroyTest" destroy-method="destroyMethod"/>
方法2.对应的类实现DisposableBean接口,然后bean标签内写一个destroy-method的属性:
import org.springframework.beans.factory.DisposableBean; public class DestroyTest implements DisposableBean { @Override public void destroy() throws Exception { System.out.println("DestroyTest.destroy"); } }
<bean id="destroy" class="com.baizhi.DestroyTest"/>
在以上两注解应用之后,Spring将会自动调用:
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Product {
@PostConstruct
public void myInit(){}
@PreDestroy
public void destroy(){}
}
5.与注入相关的注解:
主要分为两种情况:
这里先回忆一下,我们使用UserService的实现类需要UserDAO,那我们就需要在UserService实现类中声明UserDAO对象,代码如下:
UserDAO的实现类UserDAOImpl.java:
public class UserDAOImpl implements UserDAO{
@Override
......
}
UserService的实现类UserServiceImpl.java:
public class UserServiceImpl implements UserService{
private UserDAO userDAO;
//set方法
public void setUserDAO(UserDAO userDAO){this.userDAO = userDAO;}
//get方法
getUserDAO ......
@Override
......
}
我们原本的配置方法是这样的:
<bean id="userDAO" class="com.example.dao.UserDAOImpl"/>
<bean id="userService" class="com.example.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
那么现在,我们将使用注解进行和上面代码一样的操作:
接口实现类UserDAOImpl.java:
@Repository
public class UserDAOImpl implements UserDAO {
......
}
接口实现类UserServiceImpl.java:
@Service
public class UserServiceImpl implements UserService{
private UserDAO userDAO;
//set方法,这样UserDAO对象就自动的被Spring注入给了UserService
@Autowired
public void setUserDAO(UserDAO userDAO){this.userDAO = userDAO;}
//get方法
getUserDAO ......
@Override
......
}
分析:在进行依赖注入的时候,工厂将调用相应属性的set方法进行注入,所以Autowired注解相应的需要书写在set方法上。
完整代码:
UserDAO.java:
public interface UserDAO {
public void save();
}
UserDAOImpl.java:
@Repository
public class UserDAOImpl implements UserDAO {
@Override
public void save() {
System.out.println("UserDAOImpl.save");
}
}
UserService.java:
public interface UserService {
public void register();
}
UserServiceImpl.java:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
//这里使用了自定义id
@Service("userService")
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public UserDAO getUserDAO() {
return userDAO;
}
//注入
@Autowired
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public void register() {
userDAO.save();
System.out.println("UserServiceImpl.register");
}
}
测试类:
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InjectionTest {
@Test
public void test(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.register();
}
}
输出结果:
@Autowired注解使用需注意:
-
如果我们现在有一个UserDAOImpl和一个ProductDAOImpl,都添加了@Repository注解,我们之后进行Autowired注入的时候为什么能做到准确注入UserDAOImpl呢?
我们应该清楚一点:Autowired注解是基于类型进行注入的,就是说我们需要注入的类型,必须是与成员变量相同的类型或者是子类再或者是接口的实现类(如UserServiceImpl类中注入的是UserDAO接口的实现类UserDAOImpl)
-
上面一条提到Autowired是基于类型进行注入的,但是此时应用需要基于名字进行注入的话,Autowired就显得乏力了。我们可以引入一个新的注解@Qualifier,这样就可以基于名字进行注入了:
@Qualifier("userDAOImpl") @Autowired public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; }
虽说是基于名字,但实际上比较的是bean的id值,如果将UserDAOmpl的@Repository注解进行id另起的话,也是无法匹配的。
-
@Autowired注解的放置位置:我们将@Autowired注解放在set方法上,那么在创建对象的时候,就会调用set方法为对应的成员变量赋值,这样也是清晰明了。但是,我们也可以直接将@Autowired注解直接放在成员变量上,这样的注入也是成功的,但这样的方式将不会调用set方法,Spring将通过反射完成上述操作。(推荐该方法,可以使我们的代码更加的简洁)
@Autowired private UserDAO userDAO; /* 可省略 public UserDAO getUserDAO() { return userDAO; } public void setUserDAO(UserDAO userDAO) { System.out.println("UserServiceImpl.setUserDAO"); this.userDAO = userDAO; } */
-
JavaEE规范中相似功能的注解:
- JSR 250 @Resource注解,是依据名字进行注入的,在没有给出name属性的时候,可以基于类型进行注入,等价于@Autowired和@Qualifier的整合,直接书写@Resource(name = “UserDAOImpl”)即可
public class UserServiceImpl implements UserService { @Resource(name = "userDAOImpl") private UserDAO userDAO; ...... }
运行结果都是一样的。
注意:如果在应用Resource注解的过程中,名字没有配对成功,那么它会继续按照类型进行注入。
- JSR 330 @Inject注解,作用和@Autowired完全一致,基于类型进行注入,需要导包(具体就不写了,了解即可):
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
这里先回忆一下,我们定义一个User类,然后要将它在Spring配置文件中进行声明并为对应参数进行赋值,具体代码如下:
User.java:
public class User{
private Integer id;
private String name;
.
.
set和get方法等......
}
ApplicationContext.xml:
...
<bean id="user" class="com.example.entity.User">
<property name="id" value="1"/>
<property name="name" value="张三"/>
</bean>
...
可是,我们如果使用了注解开发,就不会在Spring配置文件里书写bean标签了,我们应该如何为参数进行赋值呢?
1.将属性放在xxx.properties文件中,利用键值对的方式进行赋值操作,之后使用@Value注解进行注入:
开发步骤:
首先需要在Spring配置中声明让Spring能够找到 .properties文件:
<context:property-placeholder location="classpath:application.properties"/>
其次在User.java类中使用@Value注解进行注入:
@Component
public class User{
@Value("${id}")
private Integer id;
@Value("${name}")
private String name;
.
.
set和get方法等......
}
最后在 .properties文件(本例为application.properties)定义对应的值:
id = 1
name = 张三
测试类如下:
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ValueTest {
@Test
public void test(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) ctx.getBean("user");
System.out.println(user.toString());
}
}
输出结果(注意,若输出乱码,请在设置中修改文件默认编码格式为utf-8):
2.@PropertySource注解,使配置趋于注解化
作用:用于替换在Spring配置文件中的**<context:property-placeholder location=" "/>**标签.
开发步骤:
首先依旧是需要 .properties文件:
id = 1
name = 张三
然后应用@PropertySource注解,相当于是将原本书写的location参数放到注解当中去了:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("classpath:application.properties")
public class User {
@Value("${id}")
private Integer id;
@Value("${name}")
private String name;
......
set和get等方法......
}
最后测试:
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ValueTest {
@Test
public void test(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) ctx.getBean("user");
System.out.println(user.toString());
}
}
输出结果:
@Value注解的使用注意:
-
@Value注解下的成员变量不可以是静态的(即加了static关键字),如果使用,则注入失败,会使用创建对象的默认值
-
在使用@Value+properties这种方式,是不可以注入集合类型的。
那我们应该如何注入集合类型呢?
Spring为我们提供了一种新的文件形式YAML文件,后缀名xxx.yml或者xxx.yaml:
#使用int的list student: ids: [1, 2, 3, 4, 5] #或者 stu: - 1 - 2 - 3 - 4 - 5
3.此外,直接在@Value中写参数也是可以的(推荐):
设置包扫描:
<context:component-scan base-package="com.example"/>
User.java:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value("1")
private Integer id;
@Value("李四")
private String name;
......
}
测试类不变,最终输出:
最后做一些补充:
①注解扫描:
我们应该也清楚,要扫描注解需要进行**<context:component-scan base-package=" "/>的配置,在我们输入包的时候,Spring将会基于我们所提供的包以及其子包**进行扫描。
但是这样显得很不灵活。
比如我们在com.example包中,有些注解我们不想要去扫描了,我们该怎么去做呢?
-
排除方式
排除某些类上的注解,不进行扫描,进而不创建对象
<context:component-scan base-package="com.example"> <context:exclude-filter type="assignable|annotation|aspectj|regex|custom" expression=""/> </context:component-scan>
-
assignable:排除特定的类型,不进行扫描
<context:component-scan base-package="com.example"> <context:exclude-filter type="assignable" expression="com.example.entity.User"/> </context:component-scan>
-
annotation:排除特定的注解,不进行扫描
<!--排除当前包及其子包中所有带有@Service注解的对象--> <context:component-scan base-package="com.example"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
-
aspectj:依据切入点表达式,进行排除(★重要)
<context:component-scan base-package="com.example"> <!--排除bean包及其子包的注解,不进行扫描 切入点表达式:此处只支持两种:包切入点和类切入点,具体请自行搜索--> <context:exclude-filter type="aspectj" expression="com.example.bean..*"/> </context:component-scan>
-
regex:使用正则表达式进行排除,和aspectj相似
-
custom:自定义排除策略,应用开发使用较少,略…
注:这些排除方式是可以叠加的,如:
<context:component-scan base-package="com.example"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> <context:exclude-filter type="aspectj" expression="com.example.bean..*"/> </context:component-scan>
-
-
包含方式
<context:component-scan base-package="com.example" use-default-filters="false"> <context:include-filter expression="" type="assignable|annotation|aspectj|regex|custom"/> </context:component-scan>
如果我们只要当前包及其子包下的某某类,用包含方式会更好。
- use-default-filters:直接让Spring的默认注解扫描策略失效
- <context:include-filter expression=" " type=" "/>:指定需要扫描的注解
包含方式和排除方式没有什么太大区别,只是语义相反而已,也可以叠加,在此不谈了。
②对于注解开发的思考:
-
配置互通
作为Spring来讲,无论是作为xml的配置文件,还是注解配置,都是互通的。
如下(非准确代码,理解含义即可):
@Repository("userDAO") public class UserDAOImpl implements UserDAO{} public class UserService implements UserService{ private UserDAO userDAO; ...... }
xml文件可以直接写:
<bean id="userService" class="com.example.service.UserService"> <property name="userDAO" ref="userDAO"/> </bean>
我们可以看到,在代码中注解@Repository下的UserDAOImpl(bean的id值自定义为userDAO)可以直接作为依赖项注入在配置文件中的bean中。
-
什么情况下使用注解,什么情况下使用配置文件
类是我写的,我才可以在自己的类上加@Component以及它的衍生注解。如果不是我们自己写的一些类,如在整合Mybatis的时候,我们会用到SqlSessionFactoryBean或者MapperScannerConfigure,这些类就目前来说(在Spring高级配置中解决了这些问题,但只说现在),我们需要在配置文件中配置。