前面我们已经了解了Spring是如何创建Bean以及完成依赖注入的,但我们会发现,其似乎并没有达到简化开发的目的,这是因为 我们还没有接触到注解开发。
注解开发之组件
我们在要使用Bean的地方使用@Comonpent(组件)注解,相当于在IOC容器中创建了标签,随后在里面写上名字代表bean的名字。
@Component("bookdao")
public class BookDaoImpl implements BookDao {public BookDaoImpl(){System.out.print("bookdao constructor running...\n");}public void save(){System.out.print("执行dao...\n");}public void init(){System.out.print("bookdao init...\n");}public void destory(){System.out.print("bookdao destory..\n");}
}
随后我们只需要在IOC容器中使用组件扫描包即可:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"
><context:component-scan base-package="dao.impl"/>
</beans>
此外,Spring为方便我们区分,认为都使用Component不方便,因此提供了三个衍生注解:Controller、Service以及Repository。他们三个的作用与Component完全相同,只是表示不同而已。
此时,我们看到在IOC容器中并没有太多的Bean内容了,只有一个扫描,那么,我们可不可以直接将XML去掉呢,那么就可以使用纯注解开发来实现这个功能:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration//代替整个Spring的那些头文件
@ComponentScan("dao.impl")//代替扫描组件操作
public class SpringConfig {
}
随后我们创建IOC容器并获取Bean,即由原本的读取配置文件变为了读取配置类。
import dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestSrpingConfig {public static void main(String[] args) {ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);BookDao dataSource= (BookDao) ctx.getBean("bookdao");System.out.print(dataSource);}
}
事实上,配置类的形式仅仅是将配置文件换了一种表现方式罢了。
自动装配
前面已经提到,可以用settter或构造方法的形式来实现依赖注入,那么在注解开发中是否能够将这个过程再次简化,将setter方法也去除呢?Spring实现了这个功能:Autowrited,这个功能实际上是通过反射实现的。
反射原理
反射就是让对象认清自己:
而Java语言则具备反射的能力。
Java语言中反射的实现原理。Java的反射机制可以在运行时记录类的信息,包括类名、父类、接口、变量、方法等并将其存储在一个特殊的对象中,即Instance Class。通过Java Mirror,即Java镜像,可以访问这些信息,并以Java对象的形式表示。通过一系列的API,可以间接访问Instance Class中的信息。Java的反射机制是Java语言超越C++的关键因素之一,也是Java成为世界第一大编程语言的原因之一。
那么该如何实现呢?
首先是修改Spring配置文件,由于我们增添了Service层的对象,那么我们就需要扫描Service层。使用{}来扫描多个层。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration//代表是配置文件
@ComponentScan({"dao.impl","service.impl"})//代表要扫的层
public class SpringConfig {
}
然后我们只需要在对应的ServiceImpl与OrderDaoImpl对象中添加注解即可。
package service.impl;
import dao.OrderDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import service.OrderService;
@Service("orderservice")//添加@Service注解,可以不加名字,此时加载Bean时只能通过类型加载,但这也是我们所推荐的,加了名字后就可以根据名字加载
public class OrderServiceImpl implements OrderService {@Autowired//必须使用该注解,可以代替原本的setter与构造方法的装配依赖方法。OrderDao orderdao;@Overridepublic void save() {orderdao.save();}
}
OrderDaoImpl的配置如下:
package dao.impl;
import dao.OrderDao;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.stereotype.Repository;
import java.util.*;
@Repository("orderdao")//加入持久层注解
public class OrderDaoImpl implements OrderDao {@Overridepublic void save() {System.out.print("orderdao save..");}
}
执行的话,我们使用名字来加载Bean,当然也可以按照类型。
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import service.OrderService;public class TestSrpingConfig {public static void main(String[] args) {ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);OrderService os= (OrderService) ctx.getBean("orderservice");os.save();}
}
需要提一下的是,@Autowired是通过类型进行装配的,因此如果在Service中需要加载两个相同类型的对象时,那么这时候如果只使用@Autowired则会报错。
比如我们现在创建一个OrderDaoImpl2,它也是OrderDao接口的实现类。
package dao.impl;
import dao.OrderDao;
import org.springframework.stereotype.Repository;
@Repository
public class OrderDaoImpl2 implements OrderDao {@Overridepublic void show() {}@Overridepublic void save() {System.out.print("orderdao2 save..");}
}
此时,需要注意的是,我们要将OrderDaoImpl前面的@Repository(“orderdao”)这的名字去掉,否则它还是会按照名字加载,在双方都没有名字后,我们再次运行会报错:
可以看到报错信息,找到了两个相同的类型,不知道应该加载哪一个。
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'dao.OrderDao' available: expected single matching bean but found 2: orderDaoImpl,orderDaoImpl2
此时我们将其修改一下,加上名字,这里通过@Qualifier这个注解来指定注入哪个Bean,同时我们也发现如果不加@Qualifier的话,会按照起的变量名来匹配,如下面,如果不加@Qualifier,则会找到orderdao2,但加了之后,便会加载orderdao,因此可以看到要想指定同类型的Bean进行注入,我们还是要使用@Qualifier这个注解,同时也发现,如果把@Qualifier去掉,然后起名字叫orderdao1的话,就会再次报之前的错误了。
package service.impl;
import dao.OrderDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import service.OrderService;
@Service("orderservice")
public class OrderServiceImpl implements OrderService {@Autowired@Qualifier("orderdao")OrderDao orderdao2;@Overridepublic void save() {orderdao2.save();}
}
需要注意的是,Spring是通过暴力反射的方式实现了注解开发,这事实上与IOC思想有些冲突,因此IOC的思想是创建Bean在外部,你给我提供对应的接口(setter方法)即可,而通过这种注解开发的方式,我们可以去除setter方法。
同时,注解开发是通过无参构造方法的形式实现的,因此我们必须保证它有无参构造方法,即我们只要不重写构造方法就可以了。
上面是实现引用类型注入,那么如何实现简单类型注入呢?很简单,使用Value注解即可。
那么这样使用Value注解来实现简单类型注入有啥问题呢,从上面的代码可以看出,我们的值是写在里面的,这样的话和我们直接在生成变量时便给赋值效果是相同的,那么这样写的意义在哪,事实上,这是为了方便我们从外部文件(如properties)中获取数据。
外部properties文件简单类型注入
那么我们该如何实现呢?
首先,我们要有一个先创建一个db.properties文件:
jdbc.url = jdbc\:mysql\:///ssm_takeout?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
jdbc.driver = com.mysql.jdbc.Driver
jdbc.username= root
jdbc.password= root
name=libai
随后我们需要让我们的Spring配置 文件找到这个properties文件:
@Configuration
@ComponentScan({"dao.impl","service.impl"})
@PropertySource("db.properties")//注意,这里是不能使用通配符的。
public class SpringConfig {
}
然后我们将需要获得的参数与properties文件中的属性对应一下:
@Service("orderservice")
public class OrderServiceImpl implements OrderService {@Autowired@Qualifier("orderdao")OrderDao orderdao;@Value("${name}")//这里对应name名字private String name;@Overridepublic void save() {System.out.print(name+"\n");orderdao.save();}
}
运行结果:
管理第三方Bean
我们重新定义一个Spring配置类,并使用@Bean
注解规定返回结果是一个Bean
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.beans.PropertyVetoException;
@Configuration
public class SpringConfigOtherBean {//1.定义一个方法来获取要管理的对象//2.添加@Bean注解,表示当前方法返回的是一个Bean@Beanpublic ComboPooledDataSource getDataSource() throws PropertyVetoException {ComboPooledDataSource ds = new ComboPooledDataSource();ds.setDriverClass("com.mysql.jdbc.Driver");ds.setUser("root");ds.setJdbcUrl("jdbc\\:mysql\\:///ssm_takeout");ds.setPassword("root");return ds;}
}
随后,我们运行一下:
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSrpingConfig {public static void main(String[] args) {ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfigOtherBean.class);ComboPooledDataSource ds=ctx.getBean(ComboPooledDataSource.class);System.out.print(ds);}
}
但需要注意的是,我们 先前是将数据库的配置也写到Spring的配置中了,如果我们把所有的文件都放到Spring配置中的话,这个代码的可读性就会很差,因此我们需要将这些文件独立出来。
怎么写呢,很简单,使用Import注解即可:
原始的Spring配置类如下:
package configs;import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;@Configuration
@Import(JdbcConfig.class)//导入其他配置,若有多个,则使用{}
public class SpringConfig {
}
JDBC的配置类:
package configs;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import java.beans.PropertyVetoException;
public class JdbcConfig {//1.定义一个方法来获取要管理的对象//2.添加@Bean注解,表示当前方法返回的是一个Bean@Beanpublic ComboPooledDataSource getDataSource() throws PropertyVetoException {ComboPooledDataSource ds = new ComboPooledDataSource();ds.setDriverClass("com.mysql.jdbc.Driver");ds.setUser("root");ds.setJdbcUrl("jdbc\\:mysql\\:///ssm_takeout");ds.setPassword("root");return ds;}
}
再次运行,正常。
第三方Bean依赖注入
前面在配置第三方Bean管理时,发现我们的数据库配置信息直接写在了里面,这是不允许的,我们需要修改一下:
我们首先需要加载一下db.properties文件
package configs;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;@Configuration
@Import(JdbcConfig.class)
@PropertySource("classpath:db.properties")
public class SpringConfig {
}
随后修改一下JDBCConfig中的配置:
package configs;import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import java.beans.PropertyVetoException;
public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;//1.定义一个方法来获取要管理的对象//2.添加@Bean注解,表示当前方法返回的是一个Bean@Beanpublic ComboPooledDataSource getDataSource() throws PropertyVetoException {ComboPooledDataSource ds = new ComboPooledDataSource();ds.setDriverClass(driver);ds.setUser(username);ds.setJdbcUrl(url);ds.setPassword(password);return ds;}
}
这是基本数据类型在第三方Bean中的注入方式,那么如果是其他的Bean类型呢,其实Spring的设计更为简单。
首先需要在Spring配置中扫描我们要引用的Bean所在的包:
package configs;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
@Configuration
@Import(JdbcConfig.class)
@PropertySource("classpath:db.properties")
@ComponentScan("dao")
public class SpringConfig {
}
随后我们便可以直接在第三方Bean所对应的方法中获取了,直接以形参的方式便可以实现了,这是由于我们的自动装配。
package configs;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import dao.OrderDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import java.beans.PropertyVetoException;
public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;//1.定义一个方法来获取要管理的对象//2.添加@Bean注解,表示当前方法返回的是一个Bean@Beanpublic ComboPooledDataSource getDataSource(OrderDao orderdao) throws PropertyVetoException {ComboPooledDataSource ds = new ComboPooledDataSource();System.out.print(orderdao);ds.setDriverClass(driver);ds.setUser(username);ds.setJdbcUrl(url);ds.setPassword(password);return ds;}
}
总结一下,在第三方Bean的管理中,我们需要使用@Bean注解来获取所需要的Bean,对于第三方Bean中需要获取的数据,引用类型使用方法形参,基本类型使用成员变量。