ioc也可以称为DI,即依赖注入
ioc(控制反转):对象由主动new产生对象转变为由外部提供对象。(控制反转思想:对象创建控制权由程序转移到外部。【这里的外部是指:Spring的IOC容器】)
DI(依赖注入):在ioc容器中建立bean与bean之间的依赖关系。(比如service和dao的关系:dao作为service的一个成员变量。那么这依赖关系就是service和dao之间的关系);(还没学spring之前,我们是通过service层调用dao层的方法进行实现,把dao层作为service层的一个成员变量进行引用)
ioc容器的作用:IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象,IOC容器中放的就是一个个的Bean对象(比如service层,dao层等的类对象,都被ioc容器管理,所以都是bean)。其实就是解耦。
bean:被创建或被管理的对象在IOC容器中统称为Bean
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">
<!--
bean标签就是告知ioc容器,该对象需要被管理(即配置bean对象)
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
配置文件下的各属性的意义:
BeanFactory工厂(类似反射)也可以说是:延迟加载bean
public class BeanFactoryTest {
public static void main(String[] args) {
// 1. 创建工厂对象
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 2. 创建一个读取器(xml文件)
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
// 3. 读取器绑定工厂
reader.loadBeanDefinitions("beans.xml");
// 4. 根据id获取bean实例对象
UserService userService = (UserService) beanFactory.getBean("userService");
}
}
ApplicationContext:ApplicationContext接口常用初始化类:ClassPathXmlApplicationContext(常用),这个类主要是用来加载配置文件的(.xml)
public class ApplicationContextTest {
public static void main(String[] args) {
//创建ApplicationContext,加载配置文件,实例化容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//根据beanName获得容器中的Bean实例
UserService userService = (UserService) applicationContext.getBean("userService");
}
}
反射
。
//service层下的接口
public interface BookDao {
public void save();
}
//impl层的实现类
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
//程序执行的主方法类
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
//在impl层下的接口实现类下添加一个无参构造方法(其实默认存在一个无参构造方法的,这里只是加入一条输出语句,方便看到这个无参构造方法被调用)
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
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-->
<!--这里使用的是无参构造-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<!-- 一下均为解释-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<!--基本数据类型-->
<constructor-arg name="参数名" value="参数值"/>
<!--引用数据类型-->
<constructor-arg name="参数名" ref="引用类型的对象"/>
</bean>
</beans>
//接口
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();
}
}
//执行的主方法
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
spring的配置
<?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="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
<!--
class:工厂类的类全名
factory-mehod:具体工厂类中创建对象的方法名
factory-mehod参数是告诉spring不是要把class="com.itheima.factory.OrderDaoFactory"变成对象,而是找到class=="com.itheima.factory.OrderDaoFactory"内部的方法getOrderDao这个方法,
把这个方法的返回值当做对象,再以你指定的id作为对象的名字存储到容器中
-->
</beans>
问题
/*
可能有人会问了,你这种方式在工厂类中不也是直接new对象的,
和我自己直接new没什么太大的区别,
而且静态工厂的方式反而更复杂,这种方式的意义是什么?
意义:
1. 逻辑更加灵活一点
2. 方便某一些第三方bean的使用
*/
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
//bean创建之前进行其他的业务操作
return new OrderDaoImpl();
//bean创建之后进行一些其他的逻辑操
//(可以把返回值提到外面,即OrderDao orderDao = new OrderDaoImpl())
}
}
//接口
public interface OrderDao {
public void save();
}
//接口对应的实现类
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
//静态工厂创建对象
public class OrderDaoFactory {
//普通方法,返回一个对象
public OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
//执行的主方法
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
spring的配置
<?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">
<!-- 1. 得先配置工厂对象-->
<bean id="orderDaoFactory" class="com.itheima.factory.OrderDaoFactory"/>
<!-- 2. 再去配工厂方法的返回值-->
<bean id="orderDao" factory-bean ="orderDaoFactory" factory-method="getOrderDao"/>
<!--
class:工厂类的类全名
factory-mehod:具体工厂类中创建对象的方法名
factory-bean::工厂的实例对象
-->
</beans>
问题
可能有人会问了,你这种方式在工厂类中不也是直接new对象的,
和我自己直接new没什么太大的区别,
而且静态工厂的方式反而更复杂,这种方式的意义是什么?
意义:
1. 逻辑更加灵活一点
2. 方便某一些第三方bean的使用
public class OrderDaoFactory {
public OrderDao getOrderDao(){
//bean创建之前进行其他的业务操作
return new OrderDaoImpl();
//bean创建之后进行一些其他的逻辑操
//(可以把返回值提到外面,即OrderDao orderDao = new OrderDaoImpl())
}
}
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}
spring配置
<?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">
<!-- 配置FactoryBean交由Spring管理-->
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
</beans>
执行主方法:
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
UserDao userDao1 = (UserDao) ctx.getBean("userDao");
// userDao和userDao1是同一个对象
userDao.save();
}
}
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
这种方式存在的问题是每次获取的时候都需要进行类型转换,有没有更简单的方式呢?
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
这种方式可以解决类型强转问题,但是参数又多加了一个,相对来说没有简化多少。
BookDao bookDao = ctx.getBean(BookDao.class);
这种方式就类似我们之前所学习依赖注入中的按类型注入。必须要确保IOC容器中该类型对应的bean对象只能有一个。
分为两个阶段:
关于Spring中对bean生命周期控制提供了两种方式:
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>
ClassPathXmlApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
ctx.close();
ClassPathXmlApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
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");
}
}
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;
}
}
<?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="com.itheima.dao.impl.BookDaoImpl">
<property name="databaseName" value="mysql"/>
<property name="connectionNum" value="10"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.itheima.dao.imipl.BookDaoImpl"/>
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public BookServiceImpl(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 id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
</beans>
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;
public BookServiceImpl(BookDao bookDao,UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
}
<?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="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>
如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选
依赖的注入
实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注
入
在setter注入的基础上修改:
(1)将<property>标签删除
(2)在<bean>标签中添加autowire属性
<?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 class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"
autowire="byType"/>
</beans>
需求:使用Spring的IOC容器来管理Druid连接池对象
1.使用第三方的技术,需要在pom.xml添加依赖
2.在配置文件中将【第三方的类】制作成一个bean,让IOC容器进行管理
3.数据库连接需要基础的四要素驱动、连接、用户名和密码,【如何注入】到对应的bean中(使用setter注入)
第三方的类: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>
C3P0数据源【ComboPooledDataSource】
<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>
C3P0的四个属性和Druid的四个属性是不一样的
这两个数据源中都使用到了一些固定的常量如数据库连接四要素,把这些值写在Spring的配置文件中不利于后期维护需要将这些值提取到一个外部的properties配置文件中(就是使用properties文件)
properties文件具体内容:(采用键值对的方式)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
注意
解决方案
system-properties-mode:设置为NEVER,表示不加载系统属性,就可以解决上述问题。
当然还有一个解决方案就是避免使用username作为属性的key。
<?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/springcontext.xsd">
<context:property-placeholder location="jdbc.properties" systemproperties-mode="NEVER"/>
</beans>
<?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/springcontext.xsd">
<!-- 加载properties文件-->
<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>
<?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/springcontext.xsd">
<!--方式一 -->
<context:property-placeholder
location="jdbc.properties,jdbc2.properties" system-propertiesmode="NEVER"/>
<!--方式二-->
<context:property-placeholder location="*.properties" systemproperties-mode="NEVER"/>
<!--方式三 -->
<context:property-placeholder location="classpath:*.properties"
system-properties-mode="NEVER"/>
<!--方式四-->
<context:property-placeholder location="classpath*:*.properties"
system-properties-mode="NEVER"/>
</beans>
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
@Component("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
在service层上也要添加注解@Component
为了让Spring框架能够扫描到写在类上的注解,需要在配置文件上进行包扫描
<?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">
<context:component-scan base-package="com.itheima"/>
</beans>
名称 | @Component/@Controller/@Service/@Repository |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为spring管理的bean |
属性 | value(默认) :定义bean的id |
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
//加载配置文件初始化容器
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SpringConfig.class);
名称 | @Configuration |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为spring配置类 |
属性 | value(默认) :定义bean的id |
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
属性 | value(默认):扫描路径,此路径可以逐层向下扫描 |
名称 | @Scope |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类创建对象的作用范围,可用于设置创建出的bean是否为单例对象 |
属性 | value(默认):定义bean作用范围,默认值singleton(单例),可选值prototype(非单例) |
@Repository
//@Scope设置bean的作用范围
@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 ...");
}
}
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
找不到的原因是,从JDK9以后jdk中的javax.annotation包被移除了,这两个注解刚好就在这个包
中。
名称 | @Autowired |
---|---|
类型 | 属性注解 或 方法注解(了解) 或 方法形参注解(了解) |
位置 | 属性定义上方 或 标准set方法上方 或 类set方法上方 或 方法形参前面 |
作用 | 为引用类型属性设置值 |
属性 | required:true/false,定义该属性是否允许为null |
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意:
名称 | @Qualifier |
---|---|
类型 | 属性注解 或 方法注解(了解) |
位置 | 属性定义上方 或 标准set方法上方 或 类set方法上方 |
作用 | 为引用类型属性指定注入的beanId |
属性 | value(默认):设置注入的beanId |
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
名称 | @value |
---|---|
类型 | 属性注解 或 方法注解(了解) |
位置 | 属性定义上方 或 标准set方法上方 或 类set方法上方 |
作用 | 为 基本数据类型 或 字符串类型 属性设置值 |
属性 | value(默认):要注入的属性值 |
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("itheima")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
名称 | @PropertySource |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 加载properties文件中的属性值 |
属性 | value(默认):设置加载的properties文件对应的文件名或文件名组成的数组 |
@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
//读取的properties配置文件有多个
@PropertySource({"jdbc.properties","xxx.properties"})
//classpath
@PropertySource({"classpath:jdbc.properties"})
名称 | @bean |
---|---|
类型 | 方法注解 |
位置 | 方法定义上方 |
作用 | 设置该方法的返回值作为spring管理的bean |
属性 | value(默认):定义bean的id |
举例:对Druid数据源的管理
@Configuration
public class SpringConfig {
@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 |
---|---|
类型 | 类注解 |
位置 | 定义在类上方 |
作用 | 导入配置类 |
属性 | value(默认):定义导入的配置类类名,当配置类有多个时使用数组格式一次性导入多个配置类 |
注意:
原因:如果把所有的第三方bean都配置到Spring的配置类SpringConfig中,虽然可以,但是不利于代码阅
读和分类管理,我们可以按照类别将这些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;
}
}
@Configuration
//@ComponentScan("com.itheima.config")
@Import({JdbcConfig.class})
public class SpringConfig {
}
注入引用数据类型步骤
@Configuration
@ComponentScan("com.itheima.dao")
@Import({JdbcConfig.class})
public class SpringConfig {
}
public class JdbcConfig {
@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;
}
}
<?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="com.itheima.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="com.itheima.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);
//执行user对象的方法
Account ac = accountDao.findById(1);
System.out.println(ac);
// 6. 释放资源
sqlSession.close();
}
}
思路:
步骤:
1):创建Spring的主配置类
//配置类注解
@Configuration
//包扫描,主要扫描的是项目中的AccountServiceImpl类
@ComponentScan("com.itheima")
public class SpringConfig {
}
2)创建数据源的配置类
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;
}
}
3)主配置类中读properties并引入数据源配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class)
public class SpringConfig {
}
4)创建Mybatis配置类并配置SqlSessionFactory
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//设置模型类的别名扫描
ssfb.setTypeAliasesPackage("com.itheima.domain");
//设置数据源(通过参数的方式进行传递数据源)
ssfb.setDataSource(dataSource);
return ssfb;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
//设置包扫描的路径
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
5)主配置类中引入Mybatis配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
编写一个测试类:
//设置类运行器
@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());
}
}
名称 | @RunWith |
---|---|
类型 | 测试类注解 |
位置 | 测试类上方 |
作用 | 设置JUnit运行器 |
属性 | value(默认):运行所使用的运行期 |
名称 | @ContextConfiguration |
---|---|
类型 | 测试类注解 |
位置 | 测试类上方 |
作用 | 设置JUnit加载的Spring核心配置 |
属性 | classes:核心配置类,可以使用数组的格式设定加载多个配置类locations:配置文件,可以使用数组的格式设定加载多个配置文件名称 |
1.导入坐标(pom.xml)
2.制作连接点(原始操作,Dao接口与实现类)
3.制作共性功能(通知类与通知)
4.定义切入点
5.绑定切入点与通知关系(切面)
//MyAdvice类其实就是通知类。
public class MyAdvice {
//定义切入点,@Pointcut,参数则是指定切入点的位置
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){} //pt:其实就是切入点的名字
//制作切面(绑定切入点和通知的关系),@Before:在什么之前,即通知(即共性功能)会在切入点方法执行之前执行
@Before("pt()")
public void method(){ // method方法其实就是通知
System.out.println(System.currentTimeMillis());
}
}
名称 | @Pointcut |
---|---|
类型 | 方法注解 |
位置 | 切入点方法定义上方 |
作用 | 设置切入点方法 |
属性 | value(默认):切入点表达式 |
名称 | @Before |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 |
@Component //告知spring管理这个类
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
名称 | @Aspect |
---|---|
类型 | 类注解 |
位置 | 切面类定义上方 |
作用 | 设置当前类为AOP切面类 |
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
名称 | @EnableAspectJAutoProxy |
---|---|
类型 | 配置类注解 |
位置 | 配置类定义上方 |
作用 | 开启注解格式AOP功能 |
execution(public User com.itheima.service.UserService.findById(int))
execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
public:访问修饰符,还可以是public,private等,可以省略
User:返回值,写返回值类型
com.itheima.service:包名,多级包使用点连接
UserService:类/接口名称
findById:方法名
int:参数,直接写参数的类型,多个类型用逗号隔开
异常名:方法定义中抛出指定异常,可以省略
1)* :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))
匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
2)..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
3)+:专用于匹配子类类型
execution(* *..*Service+.*(..))
这个使用率较低,描述子类的,咱们做JavaEE开发,继承机会就一次,使用都很慎重,所以很少用它。
*Service+,表示所有以Service结尾的接口的子类。
一共有5种通知类型
环绕通知:
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Around("pt()")
public void around(){
System.out.println("around before advice ...");
System.out.println("around after advice ...");
}
}
运行结果中,通知的内容打印出来,但是原始方法的内容却没有被执行。
因为环绕通知需要在原始方法的前后进行增强,所以环绕通知就必须要能对原始操作进行调用
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Around("pt()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("around before advice ...");
//表示对原始操作的调用
pjp.proceed();
System.out.println("around after advice ...");
}
}
所以如果我们使用环绕通知的话,要根据原始方法的返回值来设置环绕通知的返回值:
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
@Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
}
为什么返回的是Object而不是int的主要原因是Object类型更通用。
在环绕通知中是可以对原始方法返回值就行修改的。
注意事项:
注解:
名称 | @Before |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 |
名称 | @After |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行 |
名称 | @AfterReturning |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行 |
名称 | @AfterThrowing |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行 |
名称 | @Around |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行 |
非环绕通知获取参数:在方法上添加JoinPoint,通过JoinPoint来获取参数
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Before("pt()")
public void before(JoinPoint jp)
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
}
//...其他的略
}
环绕通知获取参数:环绕通知使用的是ProceedingJoinPoint,
因为ProceedingJoinPoint是JoinPoint类的子类,所以对于ProceedingJoinPoint类中应该也会有对应的getArgs()方法
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
//执行原始方法
Object ret = pjp.proceed();
// pjp.proceed()方法是有两个构造方法,调用无参数的proceed,
// 当原始方法有参数,会在调用的过程中自动传入参数
// 所以调用这两个方法的任意一个都可以完成功能
// 但是当需要修改原始方法的参数时,就只能采用带有参数的方法
// args[0] = 666;
// Object ret = pjp.proceed(args);
// 有了这个特性后,我们就可以在环绕通知中对原始方法的参数进行拦截过滤,
// 避免由于参数的问题导致程序无法正确运行,保证代码的健壮性。
return ret;
}
//其他的略
}
返回后通知获取返回值
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) {
System.out.println("afterReturning advice ..."+ret);
}
//其他的略
}
注意:
环绕通知获取异常:只需要将异常捕获,就可以获取到原始方法的异常信息了
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@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 throwable){
t.printStackTrace();
}
return ret;
}
//其他的略
}
抛出异常后通知获取异常
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
//其他的略
}
这里的参数名必须和throwing的值一样。
在JdbcConfig类中配置事务管理器
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;
}
}
注意:事务管理器要根据使用技术进行选择,Mybatis框架使用的是JDBC事务,可以直接使用DataSourceTransactionManage
开启事务注解,在SpringConfig(spring的核心配置类)的配置类中开启,加入@EnableTransactionManagement
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
名称 | @EnableTransactionManagement |
---|---|
类型 | 配置类注解 |
位置 | 配置类定义上方 |
作用 | 设置当前Spring环境中开启注解式事务支持 |
名称 | @Transactional |
---|---|
类型 | 接口注解 类注解 方法注解 |
位置 | 业务层接口上方 业务层实现类上方 业务方法上方 |
作用 | 为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务) |
这些属性都可以在@Transactional注解的参数上进行设置
log方法、inMoney方法和outMoney方法都属于增删改,分别有事务T1,T2,T3
transfer因为加了@Transactional注解,也开启了事务T
前面我们讲过Spring事务会把T1,T2,T3都加入到事务T中
所以当转账失败后,所有的事务都回滚,!!!!!!!!!!!!!导致日志没有记录下来!!!!!!
这和我们的需求不符,这个时候我们就想能不能让log方法单独是一个事务呢?
propagation属性
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
//propagation设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money ) {
logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
}
}