注解的存在主要是为了简化XML的配置。Spring6倡导全注解开发
package com.powernode.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
String value();
}
以上是自定义了一个注解:Component
该注解上面修饰的注解包括:Target注解和Retention注解,这两个注解被称为元注解。
Target注解用来设置Component注解可以出现的位置,以上代表表示Component注解只能用在类和接口上。
Retention注解用来设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取。
String value();是Component注解中的一个属性。该属性类型String属性名是value。
//使用User
package com.powernode.bean;
import com.powernode.annotation.Component;
@Component(value = "userBean")
public class User {
}
语法格式:@注解类型名(属性名=属性值,属性名=属性值,属性名=属性值....)
userBean为什么使用双引号括起来,因为value属性是String类型,字符串
另外如果属性名是value,则在使用的时候可以省略属性名
package com.powernode.bean;
import com.powernode.annotation.Component;
//@Component(value = "userBean")
@Component("userBean")
public class User {
}
通过反射机制怎么读取注解?
//有注解的Bean
package com.powernode.bean;
import com.powernode.annotation.Component;
@Component("userBean")
public class User {
}
//没有注解的Bean
package com.powernode.bean;
public class Vip {
}
//反射解析注解
package com.powernode.test;
import com.powernode.annotation.Component;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* @author 动力节点
* @version 1.0
* @className Test
* @since 1.0
**/
public class Test {
public static void main(String[] args) throws Exception {
// 存放Bean的Map集合。key存储beanId。value存储Bean。
Map<String,Object> beanMap = new HashMap<>();
String packageName = "com.powernode.bean";
String path = packageName.replaceAll("\\.", "/");
URL url = ClassLoader.getSystemClassLoader().getResource(path);
File file = new File(url.getPath());
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
String className = packageName + "." + f.getName().split("\\.")[0];
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Component.class)) {
Component component = clazz.getAnnotation(Component.class);
String beanId = component.value();
Object bean = clazz.newInstance();
beanMap.put(beanId, bean);
}
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(beanMap);
}
}
负责声明Bean的注解,常见的包括四个
package com.powernode.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
String value();
}
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
其实@Controller、@Service、@Repository这三个注解都是@Component注解的别名
也就是说:这四个注解的功能都一样,用哪个都可以
只是为了增强程序的可读性,建议
第一步:加入aop的依赖
当加入spring-context依赖之后,会关联加入aop的依赖。所以不需要再加什么另外的依赖
第二步:在配置文件中添加context命名空间
<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">
beans>
第三步:在配置文件中指定要扫描的包
<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.powernode.spring6.bean"/>
beans>
第四步:在Bean类上使用注解
package com.powernode.spring6.bean;
import org.springframework.stereotype.Component;
@Component(value = "userBean")
public class User {
}
//test
package com.powernode.spring6.test;
import com.powernode.spring6.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean);
}
}
如果注解的属性名是value,那么value是可以省略的。
package com.powernode.spring6.bean;
import org.springframework.stereotype.Component;
@Component("vipBean")
public class Vip {
}
package com.powernode.spring6.test;
import com.powernode.spring6.bean.Vip;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Vip vipBean = applicationContext.getBean("vipBean", Vip.class);
System.out.println(vipBean);
}
}
如果把value属性彻底去掉,spring会被Bean自动取名,默认名字的规律是Bean类名首字母小写即可
如果是多个包怎么办?有两种解决方案:
用逗号隔开
package com.powernode.spring6.bean2;
import org.springframework.stereotype.Service;
@Service
public class Order {
}
<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.powernode.spring6.bean,com.powernode.spring6.bean2"/>
beans>
//test
package com.powernode.spring6.test;
import com.powernode.spring6.bean.BankDao;
import com.powernode.spring6.bean2.Order;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
BankDao bankDao = applicationContext.getBean("bankDao", BankDao.class);
System.out.println(bankDao);
Order order = applicationContext.getBean("order", Order.class);
System.out.println(order);
}
}
指定父包
<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.powernode.spring6"/>
beans>
假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由于某种特殊业务的需求,只允许其中所有的Controller参与Bean管理,其他的都不是实例化。该如何实现
package com.powernode.spring6.bean3;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@Component
public class A {
public A() {
System.out.println("A的无参数构造方法执行");
}
}
@Controller
class B {
public B() {
System.out.println("B的无参数构造方法执行");
}
}
@Service
class C {
public C() {
System.out.println("C的无参数构造方法执行");
}
}
@Repository
class D {
public D() {
System.out.println("D的无参数构造方法执行");
}
}
@Controller
class E {
public E() {
System.out.println("E的无参数构造方法执行");
}
}
@Controller
class F {
public F() {
System.out.println("F的无参数构造方法执行");
}
}
<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.powernode.spring6.bean3" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
beans>
其中:
use-default-filters="true"
表示:使用spring默认的规则,只要有Compoment、Controller、Service、Repository中的任意一个注解标注,则进行实例化。
use-default-filters="false"
标识:不再spring默认实例化规则,即使有Component、Controller、Service、Repository这些注解标注,也不再实例化.
表示只有Controller进行实例化。
//test
@Test
public void testChoose(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-choose.xml");
}
也可以将use-default-filters设置为true(不写就是true),并且采用exclude-filter方式排除哪些注解标注的Bean不参与实例化:
<context:component-scan base-package="com.powernode.spring6.bean3">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
@Component@Controller@Service@Repository这四个注解是用来声明Bean的,声明后这些Bean将被实例化。
给Bean属性赋值需要用到这些注解:
当属性的类型是简单类型时,可以使用@Value注解进行注入
//User
package com.powernode.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value(value = "zhangsan")
private String name;
@Value("20")
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
开启包扫描
<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.powernode.spring6.bean4"/>
beans>
//test
@Test
public void testValue(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-injection.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
}
通过测试可以发现,我们并没有给属性提供setter方法,但仍然可以完成属性赋值
如果提供setter方法,并且再setter方法上添加@Value注解,更能完成此功能
package com.powernode.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private int age;
@Value("李四")
public void setName(String name) {
this.name = name;
}
@Value("30")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Value可以直接使用在属性上,也可以使用在setter方法上,都是可以的,都可以完成属性的赋值
为了简化代码,一般是不提供setter方法,直接在属性上使用@Value注解完成属性赋值。
package com.powernode.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private int age;
public User(@Value("隔壁老王") String name, @Value("33") int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Value注解可以出现在属性上、setter方法上、以及构造方法的形参上。
@Autowired注解可以用来注入非简单类型(翻译为:自动连线的,或者自动装配)
单独使用@Autowired注解,默认根据类型装配。(默认是byType)
//@Autowired源码
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
源码中有两处需要注意:
在属性上使用@Autowired注解
//
package com.powernode.spring6.dao;
public interface UserDao {
void insert();
}
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository //纳入bean管理
public class UserDaoForMySQL implements UserDao{
@Override
public void insert() {
System.out.println("正在向mysql数据库插入User数据");
}
}
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 纳入bean管理
public class UserService {
@Autowired // 在属性上注入
private UserDao userDao;
// 没有提供构造方法和setter方法。
public void save(){
userDao.insert();
}
}
<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.powernode.spring6.dao,com.powernode.spring6.service"/>
beans>
//test
@Test
public void testAutowired(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-injection.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
没有提供构造方法和setter方法,仍然可以注入成功
@Autowired注解出现在setter方法上
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
@Autowired出现在构造方法上
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
@Autowired只标注在构造方法的形参上
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
public UserService(@Autowired UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
当参数的构造方法只有一个时,@Autowired注解可以省略
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
如果有多个构造方法,@Autowired肯定是不能省略的
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public UserService(){
}
public void save(){
userDao.insert();
}
}
@Autowired注解默认是byType进行注入的,也就是说根据类型注入的,
如果以上程序中,UserDao接口还有另外一个实现类,会出现什么问题
会提示出错:不能装配,UseDao这个Bean的数量大于一
怎么解决这个问题,当然要byName,根据名称进行装配。
@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository // 这里没有给bean起名,默认名字是:userDaoForOracle
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Autowired
@Qualifier("userDaoForOracle") // 这个是bean的名字。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
总结:
@Resource注解也可以完成非简单类型注入。那它和@Autowired注解有什么区别
@Resource注解属于JDK拓展包,所以不再JDK当中,需要额外引入以下依赖(如果时JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖):
<dependency>
<groupId>jakarta.annotationgroupId>
<artifactId>jakarta.annotation-apiartifactId>
<version>2.1.1version>
dependency>
一定要注意:如果你用Spring6,要知道Spring6不再支持JavaEE,它支持的是JakartaEE9。
(Oracle把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JaKartaEE了,所以之前所接触的所有javax.*
包名统一修改为jakarta.*
包名了)
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>1.3.2version>
dependency>
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository("xyz")
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource(name = "xyz")
private UserDao userDao;
public void save(){
userDao.insert();
}
}
把UseDaoForOracle的名字xyz修改为userDao,让这个Bean的名字和UserService类中的UseDao属性名一致:
package com.powernode.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource
private UserDao userDao;
public void save(){
userDao.insert();
}
}
当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名
当通过name找不到的时候,自然会启动byType进行注入。
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Resource
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
setUserDao去掉set之后,将首字母小写userDao,userDao就是name
也可以指定name
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Resource(name = "userDaoForMySQL")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
@Resource注解:默认byName注入,没有指定name时把属性名当作name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个
省略省略省略省略,还有这个,全部省略
所谓的全注解开发就是不再使用spring配置文件了。写一个配置类来代替配置文件
package com.powernode.spring6.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.powernode.spring6.dao", "com.powernode.spring6.service"})
public class Spring6Configuration {
}
编写test程序时不用再new ClassPathXmlApplicationContext()对象了
@Test
public void testNoXml(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}