IOC(Inversion of Control)控制反转:使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
IOC容器:Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的"外部"。
Bean:被创建或被管理的对象在IOC容器中统称为Bean。
DI(Dependency Injection)依赖注入:在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。如业务层需要依赖数据层,service就要和dao建立依赖关系。
需求分析:将BookServiceImpl和BookDaoImpl交给Spring管理,并从容器中获取对应的bean对象进行方法调用。
1.创建Maven的java项目
2.pom.xml添加Spring的依赖jar包
3.创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
4.resources下添加spring配置文件,并完成bean的配置
5.使用Spring提供的接口完成IOC容器的创建
6.从容器中获取对象进行方法调用
pom.xml
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } } public interface BookService { public void save(); } public class BookServiceImpl implements BookService { private BookDao bookDao = new BookDaoImpl(); public void save() { System.out.println("book service save ..."); bookDao.save(); } }
resources下添加spring配置文件applicationContext.xml
,并完成bean的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--bean标签标示配置bean id属性标示给bean起名字 class属性表示给bean定义类型 --> <bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/> <bean id="bookService" class="fun.it.service.impl.BookServiceImpl"/> </beans>
注意事项:bean定义时id属性在同一个上下文中(配置文件)不能重复
public class App { public static void main(String[] args) { //获取IOC容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // BookDao bookDao = (BookDao) ctx.getBean("bookDao"); // bookDao.save(); BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save(); } }
结果:
在BookServiceImpl
的类中依然存在BookDaoImpl
对象的new操作,它们之间的耦合度还是比较高,就需要用到下面的DI:依赖注入
。
需求:基于IOC入门案例,在BookServiceImpl类中删除new对象的方式,使用Spring的DI完成Dao层的注入
1.删除业务层中使用new的方式创建的dao对象
2.在业务层提供BookDao的setter方法
3.在配置文件中添加依赖注入的配置
4.运行程序调用方法
在BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象
public class BookServiceImpl implements BookService { //删除业务层中使用new的方式创建的dao对象 private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } }
在BookServiceImpl类中,为BookDao提供setter方法
public class BookServiceImpl implements BookService { //删除业务层中使用new的方式创建的dao对象 private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } //提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--bean标签标示配置bean id属性标示给bean起名字 class属性表示给bean定义类型 --> <bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/> <bean id="bookService" class="fun.it.service.impl.BookServiceImpl"> <!--配置server与dao的关系--> <!--property标签表示配置当前bean的属性 name属性表示配置哪一个具体的属性 ref属性表示参照哪一个bean --> <property name="bookDao" ref="bookDao"/> </bean> </beans>
注意:配置中property的两个bookDao的含义是不一样的
bookDao
的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的setBookDao()
方法进行对象注入bookDao
的作用是让Spring能在IOC容器中找到id为bookDao
的Bean对象给bookService
进行注入id:使容器可以通过id获取对应的bean,在一个容器中id唯一
class:bean的类型,即配置的bean的全路径类名
<bean id="bookService" class="fun.it.service.impl.BookServiceImpl"/>
用来给bean配置别名,程序中可以根据别名来获取bean对象。
<bean id="bookService" name="service service4 bookEbi" class="fun.it.service.impl.BookServiceImpl" />
控制bean的作用范围,singleton
为单例模式(默认),prototype
为非单例模式。
<bean id="bookDao" name="dao" class="fun.it.dao.impl.BookDaoImpl" scope="singleton"/>
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/> </beans>
public class AppForInstanceBook { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); } }
Spring底层通过反射访问无参的构造方法,来创建bean对象。
public interface OrderDao { public void save(); } public class OrderDaoImpl implements OrderDao { public void save() { System.out.println("order dao save ..."); } }
//静态工厂创建对象 public class OrderDaoFactory { public static OrderDao getOrderDao(){ return new OrderDaoImpl(); } }
<bean id="orderDao" class="fun.it.factory.OrderDaoFactory" factory-method="getOrderDao"/>
public class AppForInstanceOrder { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); OrderDao orderDao = (OrderDao) ctx.getBean("orderDao"); orderDao.save(); } }
实例工厂实例化
public interface UserDao { public void save(); } public class UserDaoImpl implements UserDao { public void save() { System.out.println("user dao save ..."); } }
public class UserDaoFactory { public UserDao getUserDao(){ return new UserDaoImpl(); } }
<bean id="userFactory" class="fun.it.factory.UserDaoFactory"/> <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
public class AppForInstanceUser { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) ctx.getBean("userDao"); userDao.save(); } }
FactoryBean接口的使用
public class UserDaoFactoryBean implements FactoryBean<UserDao> { //代替原始实例工厂中创建对象的方法 public UserDao getObject() throws Exception { return new UserDaoImpl(); } //返回所创建类的Class对象 public Class<?> getObjectType() { return UserDao.class; } }
<bean id="userDao" class="fun.it.factory.UserDaoFactoryBean"/>
如要设置非单例模式,重写接口中的
isSingleton
方法。
bean生命周期是指bean对象从创建到销毁的整体过程。包含bean创建之前和bean销毁之后两个过程。
要观察到bean销毁的过程,需要关闭容器或注册钩子关闭容器。
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.close(); // 调用ctx的close()方法来关闭容器 // 调用ctx的registerShutdownHook()方法,让JVM在退出之前回调此函数来关闭容器 ctx.registerShutdownHook();
控制bean生命周期的两种方式:
方式一:
public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } //表示bean初始化对应的操作 public void init(){ System.out.println("init..."); } //表示bean销毁前对应的操作 public void destory(){ System.out.println("destory..."); } }
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
方式二:
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { private BookDao bookDao; public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } public void save() { System.out.println("book service save ..."); bookDao.save(); } public void destroy() throws Exception { System.out.println("service destroy"); } public void afterPropertiesSet() throws Exception { System.out.println("service init"); } }
配置文件中不需要额外配置,初始化方法会在类中属性设置之后(set操作)执行。
// 通过类路径下的XML配置文件创建 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 通过文件系统下的XML配置文件创建 ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
// 方式一 BookDao bookDao = (BookDao) ctx.getBean("bookDao"); // 方式二 BookDao bookDao = ctx.getBean("bookDao",BookDao.class); // 方式三 BookDao bookDao = ctx.getBean(BookDao.class);
通过使用BeanFactory来创建IOC容器
public class AppForBeanFactory { public static void main(String[] args) { Resource resources = new ClassPathResource("applicationContext.xml"); BeanFactory bf = new XmlBeanFactory(resources); BookDao bookDao = bf.getBean(BookDao.class); bookDao.save(); } }
BeanFactory是延迟加载,只有在获取bean对象的时候才会去创建
ApplicationContext是立即加载,容器加载的时候就会创建bean对象
配置ApplicationContext为延迟加载
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl" lazy-init="true"/>
public class BookServiceImpl implements BookService { private BookDao bookDao; private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
配置文件:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/> <bean id="userDao" class="fun.it.dao.impl.UserDaoImpl"/> <bean id="bookService" class="fun.it.service.impl.BookServiceImpl"> <!--配置中使用property标签ref属性注入引用类型对象--> <property name="bookDao" ref="bookDao"/> <property name="userDao" ref="userDao"/> </bean>
public class BookDaoImpl implements BookDao { private String databaseName; private int connectionNum; public void setConnectionNum(int connectionNum) { this.connectionNum = connectionNum; } public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } }
配置文件:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"> <property name="databaseName" value="mysql"/> <property name="connectionNum" value="10"/> </bean>
public class BookServiceImpl implements BookService{ private BookDao bookDao; private UserDao userDao; public BookServiceImpl(BookDao bookDao,UserDao userDao) { this.bookDao = bookDao; this.userDao = userDao; } }
配置文件:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/> <bean id="userDao" class="fun.it.dao.impl.UserDaoImpl"/> <bean id="bookService" class="fun.it.service.impl.BookServiceImpl"> <constructor-arg name="bookDao" ref="bookDao"/> <constructor-arg name="userDao" ref="userDao"/> </bean>
标签
<constructor-arg>
中,name属性对应的值为构造函数中方法形参的参数名,必须要保持一致
<contructor-arg>
的配置顺序可以任意
public class BookDaoImpl implements BookDao { private String databaseName; private int connectionNum; public BookDaoImpl(String databaseName, int connectionNum) { this.databaseName = databaseName; this.connectionNum = connectionNum; } }
配置文件:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"> <constructor-arg name="databaseName" value="mysql"/> <constructor-arg name="connectionNum" value="666"/> </bean>
其他写法:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"> <constructor-arg type="int" value="10"/> <constructor-arg type="java.lang.String" value="mysql"/> </bean>
其他写法:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"> <constructor-arg index="1" value="100"/> <constructor-arg index="0" value="mysql"/> </bean>
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } } public interface BookService { public void save(); } public class BookServiceImpl implements BookService{ private BookDao bookDao; public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } public void save() { System.out.println("book service save ..."); bookDao.save(); } }
配置文件:
<bean class="fun.it.dao.impl.BookDaoImpl"/> <!--autowire属性:开启自动装配,通常使用按类型装配--> <bean id="bookService" class="fun.it.service.impl.BookServiceImpl" autowire="byType"/> <!--一个类型在IOC中有多个对象时可以按照名称注入--> <bean id="bookService" class="fun.it.service.impl.BookServiceImpl" autowire="byName"/>
注意
- 需要注入属性的类中对应属性的setter方法不能省略
- 被注入的对象必须要被Spring的IOC容器管理
- 按名称注入指的是set方法去掉set后首字母小写得到的名字
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { private int[] array; private List<String> list; private Set<String> set; private Map<String,String> map; private Properties properties; public void save() { System.out.println("book dao save ..."); System.out.println("遍历数组:" + Arrays.toString(array)); System.out.println("遍历List" + list); System.out.println("遍历Set" + set); System.out.println("遍历Map" + map); System.out.println("遍历Properties" + properties); } //setter....方法省略,自己使用工具生成 }
<property name="array"> <array> <value>100</value> <value>200</value> <value>300</value> </array> </property>
<property name="list"> <list> <value>itcast</value> <value>itheima</value> <value>boxuegu</value> <value>chuanzhihui</value> </list> </property>
<property name="set"> <set> <value>itcast</value> <value>itheima</value> <value>boxuegu</value> <value>boxuegu</value> </set> </property>
<property name="map"> <map> <entry key="country" value="china"/> <entry key="province" value="henan"/> <entry key="city" value="kaifeng"/> </map> </property>
<property name="properties"> <props> <prop key="country">china</prop> <prop key="province">henan</prop> <prop key="city">kaifeng</prop> </props> </property>
<property name="array"> <array> <ref bean="beanId1"> <ref bean="beanId2"> </array> </property>
property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写
<array>
、<list>
、<set>
、<map>
、<props>
标签
准备环境:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); } }
druid
的依赖<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
在applicationContext.xml配置文件中添加DruidDataSource
的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--管理DruidDataSource对象--> <bean class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> </beans>
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) ctx.getBean("dataSource"); System.out.println(dataSource); } }
打印如下结果: 说明第三方bean对象已经被spring的IOC容器进行管理
C3P0
的依赖<dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> <!--C3P0在初始化的时候需要去加载驱动--> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
查找依赖的坐标:从mvn的仓库https://mvnrepository.com/
中进行搜索。
在applicationContext.xml配置文件中添加配置
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/> <property name="user" value="root"/> <property name="password" value="root"/> <property name="maxPoolSize" value="1000"/> </bean>
resources下创建一个jdbc.properties
文件,并添加对应的属性键值对
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db jdbc.username=root jdbc.password=root
context
命名空间在applicationContext.xml中开context
命名空间
<?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"> </beans>
在配置文件中使用context
命名空间下的标签来加载properties配置文件
<context:property-placeholder location="jdbc.properties"/>
使用${key}
来读取properties配置文件中的内容并完成属性注入
<?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:property-placeholder location="jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { private String name; public void setName(String name) { this.name = name; } public void save() { System.out.println("book dao save ..." + name); } }
配置文件:
<?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:property-placeholder location="jdbc.properties"/> <bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"> <property name="name" value="${jdbc.driver}"/> </bean> </beans>
username
键值对的key为username
时,属性注入时会将系统变量中的username进行注入,即自己电脑的用户名。设置context的system-properties-mode属性为NEVER,表示不加载系统属性,就可以解决上述问题。
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
<!--方式一 --> <context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/> <!--方式二--> <context:property-placeholder location="*.properties" system-properties-mode="NEVER"/> <!--方式三 --> <context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/> <!--方式四--> <context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
说明:
*.properties
代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准classpath:
代表的是从根路径下开始查找,但是只能查询当前项目的根路径<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
public interface BookDao { public void save(); } @Component("bookDao") public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..." ); } } public interface BookService { public void save(); } // 注解如果不起名称,会有一个默认值就是当前类名首字母小写 @Component public class BookServiceImpl implements BookService { public void save() { System.out.println("book service save ..."); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--配置Spring的注解包扫描--> <context:component-scan base-package="fun.it"/> </beans>
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); BookService bookService = (BookService)ctx.getBean("bookServiceImpl"); System.out.println(bookService); } }
衍生注解:
@Component注解,有三个衍生注解@Controller
、@Service
、@Repository
,便于区分出这个类是属于表现层
、业务层
还是数据层
的类。
创建配置类SpringConfig
,标识该类为配置类,并添加包扫描注解。
@Configuration @ComponentScan("fun.it") public class SpringConfig { }
public class AppForAnnotation { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); System.out.println(bookDao); BookService bookService = ctx.getBean(BookService.class); System.out.println(bookService); } }
@scope
注解@Repository // @Scope设置bean的作用范围 // 单例singleton(默认),非单例prototype @Scope("prototype") public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } }
@PostConstruct
和@PreDestroy
注解@Repository public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } @PostConstruct //在构造方法之后执行,替换 init-method public void init() { System.out.println("init ..."); } @PreDestroy //在销毁方法之前执行,替换 destroy-method public void destroy() { System.out.println("destroy ..."); } }
注意:@PostConstruct和@PreDestroy注解如果找不到,需要导入下面的jar包。从JDK9以后jdk中的javax.annotation包被移除了,这两个注解刚好就在这个包中。
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
使用@Autowired
注解,可以写在属性上,也可也写在setter方法上。
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; // 自动装配基于反射为属性进行赋值,不需要set方法 // public void setBookDao(BookDao bookDao) { // this.bookDao = bookDao; // } public void save() { System.out.println("book service save ..."); bookDao.save(); } }
使用@Qualifier
注解,@Qualifier
不能独立使用,必须和@Autowired
一起使用。
@Service public class BookServiceImpl implements BookService { @Autowired @Qualifier("bookDao1") private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } }
使用@Value
注解,将值写入注解的参数中即可。
@Repository("bookDao") public class BookDaoImpl implements BookDao { @Value("MyBook") private String name; public void save() { System.out.println("book dao save ..." + name); } }
在配置类上添加@PropertySource
注解
@Configuration @ComponentScan("fun.it") @PropertySource("jdbc.properties") public class SpringConfig { }
@Repository("bookDao") public class BookDaoImpl implements BookDao { @Value("${name}") private String name; public void save() { System.out.println("book dao save ..." + name); } }
@Component
作用:设置该类为spring管理的bean
用法:写在类上方
备注:
@Configuration
作用:设置该类为spring配置类
用法:写在配置类上方
@ComponentScan
作用:设置spring配置类扫描路径,用于加载使用注解格式定义的bean
用法:写在配置类上方,@ComponentScan("com.qq")
备注:
@Scope
作用:设置该类创建对象的作用范围,可用于设置创建出的bean是否为单例对象
用法:写在类上方
备注:value(默认):定义bean作用范围,默认值singleton(单例),可选值prototype(非单例)
@PostConstruct
作用:设置该方法为初始化方法
用法:写在方法上
@PreDestroy
作用:设置该方法为销毁方法
用法:写在方法上
@Autowired
作用:按属性类型自动装配,为引用类型属性设置值
用法:属性定义上方或set方法上方
备注:属性required:true/false,定义该装配的属性是否允许为null
@Qualifier
作用:为引用类型属性指定注入的beanId
用法:属性定义上方或set方法上方,@Qualifier("bookDao1")
备注:需搭配@Autowired
一起使用
@Value
作用:为基本数据类型或字符串类型属性设置值
用法:属性定义上方或set方法上方,@Value("QQ")
@PropertySource
作用:加载properties文件中的属性值
用法:写在类上方,@PropertySource("jdbc.properties")
备注:
@PropertySource
注解属性中不支持使用通配符*
classpath:
加上,代表从当前项目的根路径找文件,@PropertySource({"classpath:jdbc.properties"})@Bean
作用:设置该方法的返回值作为spring管理的bean
用法:写在方法上方
备注:属性value(默认):定义bean的id
@Import
作用:导入配置类
用法:写在类上方,@Import(JdbcConfig.class)
备注:多个配置类时,@Import({JdbcConfig.class,Xxx.class})
@RunWith
作用:设置JUnit运行器
用法:写在测试类上方,@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
作用:设置JUnit加载的Spring核心配置
用法:写在测试类上方
备注:
@EnableAspectJAutoProxy
作用:开启注解格式AOP功能
用法:写在配置类上方
@Aspect
作用:设置当前类为AOP切面类
用法:写在切面类定义上方
@Pointcut
作用:设置切入点方法
用法:写在切入点方法定义上方
备注:切入点表达式
@Pointcut("execution(* fun.it.*.*Service.*(..))")
@Before
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
用法:写在通知方法定义上方,@Before("pt()")
@After
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
用法:写在通知方法定义上方,@After("pt()")
@AfterReturning
作用:设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行
用法:写在通知方法定义上方,@AfterReturning("pt()")
@AfterThrowing
作用:设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
用法:写在通知方法定义上方,@AfterThrowing("pt()")
@Around
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
用法:写在通知方法定义上方,@Around("pt()")
@EnableTransactionManagement
作用:设置当前Spring环境中开启注解式事务支持
用法:写在配置类定义上方
@Transactional
作用:为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)
用法:写在业务层接口上方 业务层实现类上方 业务方法上方
备注:事务属性
作用:
用法:
备注:
作用:
用法:
备注:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
JdbcConfig
配置类,在返回bean的方法上添加@Bean
注解public class JdbcConfig { @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/spring_db"); ds.setUsername("root"); ds.setPassword("root"); return ds; } }
@Import
引入@Configuration // 也可以在Jdbc配置类上加@Configuration注解,然后去扫描 //@ComponentScan("fun.it.config") @Import({JdbcConfig.class}) public class SpringConfig { }
public class JdbcConfig { @Value("com.mysql.jdbc.Driver") private String driver; @Value("jdbc:mysql://localhost:3306/spring_db") private String url; @Value("root") private String userName; @Value("password") private String password; @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }
在返回bean的方法中设置形参即可,容器会根据类型自动装配对象。
@Bean public DataSource dataSource(BookDao bookDao){ System.out.println(bookDao); DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; }
创建数据库和表
create database spring_db character set utf8; use spring_db; create table tbl_account( id int primary key auto_increment, name varchar(35), money double );
项目的pom.xml添加相关依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> </dependencies>
public class Account implements Serializable { private Integer id; private String name; private Double money; //setter...getter...toString...方法略 }
public interface AccountDao { @Insert("insert into tbl_account(name,money)values(#{name},#{money})") void save(Account account); @Delete("delete from tbl_account where id = #{id} ") void delete(Integer id); @Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ") void update(Account account); @Select("select * from tbl_account") List<Account> findAll(); @Select("select * from tbl_account where id = #{id} ") Account findById(Integer id); }
public interface AccountService { void save(Account account); void delete(Integer id); void update(Account account); List<Account> findAll(); Account findById(Integer id); } @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void save(Account account) { accountDao.save(account); } public void update(Account account){ accountDao.update(account); } public void delete(Integer id) { accountDao.delete(id); } public Account findById(Integer id) { return accountDao.findById(id); } public List<Account> findAll() { return accountDao.findAll(); } }
resources目录下添加,用于配置数据库连接四要素
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false jdbc.username=root jdbc.password=root
useSSL:关闭MySQL的SSL连接
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--读取外部properties配置文件--> <properties resource="jdbc.properties"></properties> <!--别名扫描的包路径--> <typeAliases> <package name="fun.it.domain"/> </typeAliases> <!--数据源--> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </dataSource> </environment> </environments> <!--映射文件扫描包路径--> <mappers> <package name="fun.it.dao"></package> </mappers> </configuration>
public class App { public static void main(String[] args) throws IOException { // 1. 创建SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // 2. 加载SqlMapConfig.xml配置文件 InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 3. 创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); // 4. 获取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 5. 执行SqlSession对象执行查询,获取结果User AccountDao accountDao = sqlSession.getMapper(AccountDao.class); Account ac = accountDao.findById(1); System.out.println(ac); // 6. 释放资源 sqlSession.close(); } }
<dependency> <!--Spring操作数据库需要该jar包--> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <!-- Spring与Mybatis整合的jar包 这个jar包mybatis在前面,是Mybatis提供的 --> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency>
//配置类注解 @Configuration //包扫描,主要扫描的是项目中的AccountServiceImpl类 @ComponentScan("fun.it") public class SpringConfig { }
在配置类中完成数据源的创建
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; @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }
@Configuration @ComponentScan("fun.it") @PropertySource("classpath:jdbc.properties") @Import(JdbcConfig.class) public class SpringConfig { }
public class MybatisConfig { //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象 @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); //设置模型类的别名扫描 ssfb.setTypeAliasesPackage("fun.it.domain"); //设置数据源 ssfb.setDataSource(dataSource); return ssfb; } //定义bean,返回MapperScannerConfigurer对象 @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("fun.it.dao"); return msc; } }
@Configuration @ComponentScan("fun.it") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { }
public class App2 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); AccountService accountService = ctx.getBean(AccountService.class); Account ac = accountService.findById(1); System.out.println(ac); } }
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency>
//设置类运行器 @RunWith(SpringJUnit4ClassRunner.class) //设置Spring环境对应的配置类 @ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类 //@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件 public class AccountServiceTest { //支持自动装配注入bean @Autowired private AccountService accountService; @Test public void testFindById(){ System.out.println(accountService.findById(1)); } @Test public void testFindAll(){ System.out.println(accountService.findAll()); } }
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。可以在不惊动原始设计的基础上为其进行功能增强。内部使用了代理模式。
连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。在SpringAOP中,理解为方法的执行。
切入点(Pointcut):匹配连接点的式子。在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法。
通知(Advice):在切入点处执行的操作,也就是共性功能。在SpringAOP中,功能最终以方法的形式呈现。
通知类:定义通知的类。
切面(Aspect):描述通知与切入点的对应关系。
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
public interface BookDao { public void save(); public void update(); } @Repository public class BookDaoImpl implements BookDao { public void save() { System.out.println(System.currentTimeMillis()); System.out.println("book dao save ..."); } public void update(){ System.out.println("book dao update ..."); } }
@Configuration @ComponentScan("fun.it") public class SpringConfig { }
public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); bookDao.save(); } }
目标:使用SpringAOP的方式在不改变update方法的前提下让其具有打印系统时间的功能。
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
spring-context
中已经导入了spring-aop
,所以不需要再单独导入spring-aop
环境准备的时候,BookDaoImpl已经准备好,不需要做任何修改。
通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印。
需要将通知类配给容器并标识其为切面类。
@Component @Aspect public class MyAdvice { public void method(){ System.out.println(System.currentTimeMillis()); } }
类名和方法名没有要求,可以任意。
@Component @Aspect public class MyAdvice { // 切入点 @Pointcut("execution(void fun.it.dao.BookDao.update())") private void pt(){} public void method(){ System.out.println(System.currentTimeMillis()); } }
切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
@Component @Aspect public class MyAdvice { // 切入点 @Pointcut("execution(void fun.it.dao.BookDao.update())") private void pt(){} // 切面 @Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }
@Configuration @ComponentScan("fun.it") @EnableAspectJAutoProxy public class SpringConfig { }
public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); bookDao.update(); } }
结果:
工作流程:
加载被增强的类和aop通知类,此时bean对象还没有被创建。
读取被配置并且被使用的切入点。
判定bean对应的类中的方法是否匹配到任意切入点
核心概念:
execution(public User fun.it.service.UserService.findById(int))
通配符:
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * fun.it.*.UserService.find*(*))
匹配fun.it包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
..
:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
+
:专用于匹配子类类型
execution(* *..*Service+.*(..))
使用率较低,描述子类。*Service+,表示所有以Service结尾的接口的子类。
书写技巧:
追加功能到方法执行前。
@Component @Aspect public class MyAdvice { @Pointcut("execution(void fun.it.dao.BookDao.update())") private void pt(){} @Before("pt()") //此处也可以写成 @Before("MyAdvice.pt()"),不建议 public void before() { System.out.println("before advice ..."); } }
追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行。
@Component @Aspect public class MyAdvice { @Pointcut("execution(void fun.it.dao.BookDao.update())") private void pt(){} @After("pt()") public void after() { System.out.println("after advice ..."); } }
可以追加功能到方法执行的前后,它可以实现其他四种通知类型的功能。
@Component @Aspect public class MyAdvice { @Pointcut("execution(void fun.it.dao.BookDao.update())") private void pt(){} @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("around before advice ..."); //表示对原始操作的调用 Object ret = pjp.proceed(); System.out.println("around after advice ..."); return ret; } }
追加功能到方法执行后,只有方法正常执行结束后才进行。
@Component @Aspect public class MyAdvice { @Pointcut("execution(int fun.it.dao.BookDao.select())") private void pt2(){} @AfterReturning("pt2()") public void afterReturning() { System.out.println("afterReturning advice ..."); } }
追加功能到方法抛出异常后,只有方法执行出异常才进行。
@Component @Aspect public class MyAdvice { @Pointcut("execution(int fun.it.dao.BookDao.select())") private void pt2(){} @AfterReturning("pt2()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); } }
@Around("ProjectAdvice.servicePt()") public void runSpeed(ProceedingJoinPoint pjp) throws Throwable { //获取执行的签名对象 Signature signature = pjp.getSignature(); //通过签名获取执行操作名称(接口名) String className = signature.getDeclaringTypeName(); //通过签名获取执行操作名称(方法名) String methodName = signature.getName(); long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { pjp.proceed(); } long end = System.currentTimeMillis(); System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms"); }
@Around("pt()") public Object around(ProceedingJoinPoint pjp) { // 环绕通知通过ProceedingJoinPoint对象来获取参数 Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); args[0] = 666; Object ret = null; try { // 可以在执行切入点时放入修改过的参数 ret = pjp.proceed(args); } catch (Throwable t) { t.printStackTrace(); } return ret; } @Before("pt()") public void before(JoinPoint jp) { // 通过JoinPoint对象来获取执行切入点的参数 // 除Around外,其余的都相同 Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); System.out.println("before advice ..." ); }
对于返回值,只有返回后AfterReturing
和环绕Around
这两个通知类型可以获取。
@Around("pt()") public Object around(ProceedingJoinPoint pjp) { // 环绕通知通过ProceedingJoinPoint对象来获取参数 Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); args[0] = 666; Object ret = null; try { // 获取返回值 ret = pjp.proceed(args); } catch (Throwable t) { t.printStackTrace(); } return ret; } @AfterReturning(value = "pt()",returning = "ret") public void afterReturning(JoinPoint jp,String ret) { System.out.println("afterReturning advice ..."+ret); }
注意:
- returning中的变量名与方法中接受返回值的参数名保持一致
- 为了能匹配更多的参数类型,接返回值的参数类型建议写成Object类型
- 如果有Join Point参数,参数必须要放在第一位
对于获取抛出的异常,只有抛出异常后AfterThrowing
和环绕Around
这两个通知类型可以获取。
@Around("pt()") public Object around(ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); args[0] = 666; Object ret = null; try { ret = pjp.proceed(args); } catch (Throwable t) { t.printStackTrace(); } return ret; } @AfterThrowing(value = "pt()",throwing = "t") public void afterThrowing(Throwable t) { System.out.println("afterThrowing advice ..."+t); }
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
@Transactional
public interface AccountService { /** * 转账操作 * @param out 传出方 * @param in 转入方 * @param money 金额 */ //配置当前接口方法具有事务 public void transfer(String out,String in ,Double money) ; } @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Transactional public void transfer(String out,String in ,Double money) { accountDao.outMoney(out,money); int i = 1/0; accountDao.inMoney(in,money); } }
注意:
@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上
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; @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } //配置事务管理器,mybatis使用的是jdbc事务 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class //开启注解式事务驱动 @EnableTransactionManagement public class SpringConfig { }
会发现在转换的业务出现错误后,事务就可以控制回顾,保证数据的正确性。
上面这些属性都可以在@Transactional
注解的参数上进行设置。
readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
rollbackFor:当出现指定异常进行事务回滚
Error异常
和RuntimeException异常
及其子类进行事务回顾,其他的异常类型是不会回滚的noRollbackFor:当出现指定异常不进行事务回滚
rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串
noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串
isolation设置事务的隔离级别
//propagation设置事务属性:传播行为设置为当前操作需要新事务 @Transactional(propagation = Propagation.REQUIRES_NEW)
事务传播行为的可选值: