【为什么要学?】
2)、 Spring可以框架整合,高效整合其他技术,提高企业级应用开发与运行效率
【学什么?】
【怎么学?】
Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能。
【解决办法】:
我们就想,如果能把框中的内容给去掉,不就可以降低依赖了么,但是又会引入新的问题,去掉以后程序能运行么?
答案肯定是不行,因为bookDao没有赋值为Null,强行运行就会出空指针异常。
所以现在的问题就是,业务层不想new对象,运行的时候又需要这个对象,该咋办呢?
针对这个问题,Spring就提出了一个解决方案:
使用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象。而这种对象的控制权由程序转移到外部,这种思想就叫做控制反转IOC(Inversion of Control)
【IOC(Inversion of Control)控制反转】:使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
Spring技术对IOC思想进行了实现:
【DI(Dependency Injection)依赖注入】:在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
【总结】:IoC和DI两个概念的最终目标就是:充分解耦。
【思路分析:以下说明与实现步骤顺序同步】:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.10.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
//放在Dao目录下
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
//放在Service目录下
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();
}
}
<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"/>
beans>
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
【从容器中获取对象进行方法调用】:
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();
}
}
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();
}
}
【小结】:
Spring的IOC入门案例已经完成,但是在BookServiceImpl的类中依然存在BookDaoImpl对象的new操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的DI:依赖注入。
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;//删除 = new BookDaoImpl();
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
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;
}
}
<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">
<property name="bookDao" ref="bookDao"/>
bean>
beans>
【注意】:
【bean基础配置】:
【bean别名的配置】:bean的别名(name)相当于id的可选项
【bean的作用范围scope配置】:用来指定该bean是否为单例对象
说明:
public class AppForScope {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao1);
System.out.println(bookDao2);
}
}
将scope设置为singleton:bookDao1与bookDao2是同一个对象
将scope设置为prototype:bookDao1与bookDao2不是同一个对象
为什么bean默认为单例?—>bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高
哪些bean对象适合交给容器进行管理?—>表现层对象、业务层对象、数据层对象、工具对象
哪些bean对象不适合交给容器进行管理?—>封装实例的域对象,因为会引发线程安全问题,所以不适合。
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {//修饰词改成private,我们也能从容器中获得对象,说明用了反射构造对象
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
运行程序,如果控制台有打印构造函数中的输出,说明Spring容器在创建对象的时候也走的是构造函数
带参数的构造函数
public class BookDaoImpl implements BookDao {
public BookDaoImpl(int i) {//修饰词改成private,我们也能从容器中获得对象,说明用了反射构造对象
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
结论:运行程序,程序会报错,说明Spring底层使用的是类的无参构造方法
//在OrderDao接口目录下写
public interface OrderDao {
public void save();
}
//在OrderDaolmpl下写
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
创建一个工厂类OrderDaoFactory并提供一个静态方法
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
在spring的配置文件application.properties中添加以下内容:
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factorymethod="getOrderDao"/>
编写AppForInstanceOrder运行类,在类中通过工厂获取对象
public class AppForInstanceOrder {
public static void main(String[] args) {
//通过静态工厂创建对象
//OrderDao orderDao = OrderDaoFactory.getOrderDao();
//orderDao.save();
//从容器中获得对象
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 ...");
}
}
创建一个工厂类OrderDaoFactory并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
在spring的配置文件中添加以下内容
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
编写AppForInstanceUser运行类,在类中通过工厂获取对象
public class AppForInstanceUser {
public static void main(String[] args) {
//创建实例工厂对象
//UserDaoFactory userDaoFactory = new UserDaoFactory();
//通过实例工厂对象创建对象
//UserDao userDao = userDaoFactory.getUserDao();
//userDao.save();
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;
}
default boolean isSingleton() {//true是单例模式,false是多例模式
return true;
}
}
在Spring的配置文件中进行配置
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
【概述】:
bean生命周期是bean对象从创建到销毁的整体过程。bean生命周期控制是在bean创建后到销毁前做一些事情。
【生命周期控制的两种方式】:
具体的bean对象:
//BookDaoImpl类BookDao接口
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
//BookServiceImpl类BookeService接口
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();
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="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
主方法,获取bean对象
public class AppForLifeCycle {
public static void main( String[] args ) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
结果:bean对象获取创建后,初始化init方法能实现,但destroy方法不能实现,是因为容器没有关闭,因此我们要使用ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.close();
关闭destroy方法才能实现(或者使用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;
}
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
配置文件中进行注入配置:在applicationContext.xml配置文件中使用property标签注入
<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>
public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;
改为public BookServiceImpl(BookDao bookDao) { this.bookDao = bookDao;(它是BookServiceImpl类的构造器)
}
改为
【其中name指向的是构造器中的形参bookDao】【简介】:IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配,即删除了麻烦的配置依赖的操作(将
标签删除),并在
标签中添加autowire
属性(让它自动地在容器根据 依赖的对象类型-bean中的class属性(autowire=byType)
或者依赖的对象名字-bean标签的id (autowire=byName)
里找依赖的对象)
【示例】:
<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">
<property name="bookDao" ref="bookDao"/>
bean>
beans>
改为:
<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"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
beans>
【注意】:
【举例一个array】:
public class BookDaoImpl implements BookDao {
private int[] array;
public void setArray(int [ ] array){this.array = array}
public void save(){}
<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="array">
<array>
<value>100value>
<value>200value>
<value>300value>
array>
property>
bean>
beans>
【其他】:
<property name="list">
<list>
<value>itcastvalue>
<value>itheimavalue>
<value>boxueguvalue>
<value>chuanzhihuivalue>
list>
property>
<property name="set">
<set>
<value>itcastvalue>
<value>itheimavalue>
<value>boxueguvalue>
<value>boxueguvalue>
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">chinaprop>
<prop key="province">henanprop>
<prop key="city">kaifengprop>
props>
property>
【场景】: 前面所讲的知识点都是基于我们自己写的类,现在如果有需求让我们去管理第三方jar包中的类,该如何管理?
我们将通过一个案例来学习下对于第三方bean该如何进行配置管理。以后我们会用到很多第三方的bean,本次案例将使用咱们前面提到过的Alibaba的数据源Druid(德鲁伊)和C3P0来配置学习下。
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.10.RELEASEversion>
dependency>
//配置Druid数据源的坐标
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
<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>
说明:driverClassName
:数据库驱动;url
:数据库连接地址;username
:数据库连接用户名;password
:数据库连接密码;数据库连接的四要素要和自己使用的数据库信息一致。
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);
}
}片
【properties文件简单介绍】:
【问题】:由于上面把数据库信息直接写在数据源bean的配置上不好(以后需要改数据库信息的时候,我们还要改代码),所以我们把他放入properties文件(属性文件,方便修改),所以我们也得把properties文件交给容器管理即加载properties文件
【步骤】:1)、把properties加载进来 2)、改变上面标签数据库信息直接暴露的问题
创建properties文件
在里面加入数据库信息键值对
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
<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"/>加载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>
注意:普通的bean对象也能访问properties属性文件里的内容,因为这就是注入基本数据类型的方式
systemproperties-mode="NEVER"
<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"/>
说明:
方式一:可以实现,如果配置文件多的话,每个都需要配置
方式二: *.properties代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准
方式三:标准的写法,classpath:代表的是从根路径下开始查找,但是只能查询当前项目的根路径
方式四:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的properties配置文件
【容器的创建方式】:
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext ctx = 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);
【注解开发】:用注解的方式代替bean标签达到将对象放入容器的目的
【component-scan相当于扫描component注解,com.itheima相当于在这个包下扫描】
<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>
【纯注解开发】:在上面的注解开发的基础上直接用一个注解类代替整个配置文件application.xml
【步骤】:
public class SpringConfig {
}
@Configuration
public class SpringConfig {
}
@Configuration
@ComponentScan("com.itheima")
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);
}
}
@ComponentScan({com.itheima.service","com.itheima.dao"})
【作用范围】:bean是单例还是多例,通过注解@Scope
来设置,有singleton
和prototype
两种可选值
示例:
@Repository
//@Scope设置bean的作用范围
@Scope("prototype")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
【生命周期管理】:在BookDaoImpl添加init和destory方法(方法名可以任意取),只需要在对应的方法上添加@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
注解如果找不到,需要在pom.xml导入下面的jar包<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>1.3.2version>
dependency>
【环境】:
//添加一个配置类SpringConfig
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
……………………………………………………………………………………………………………………………………………………………………………………
//添加数据层bean(BookDao、BookDaoImpl类)
public interface BookDao {
public void save();
}
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
public interface BookService {
public void save();
}
添加服务层bean(BookService、BookServiceImpl类)
@Service
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();
}
}
……………………………………………………………………………………………………………………………………………………………………………………
//创建运行类App
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookService bookService = ctx.getBean(BookService.class);
bookService.save();
}
}
@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();
}
}
注意:@Autowired
是默认按照类型注入,那么对应BookDao接口如果有多个实现类,比如添加下面的实现BookDao的另一个实现类BookDaoImpl2,它就无法注入,这时就必须使用第二种注入方式按名称注入
@Repository
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...2");
}
}
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...2" );
}
}
以上其实就可以了,但是想要指定哪个对象注入,就必须使用注解@@Qualifier
如下:
@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("itheima")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
这样又把值暴露在了代码里,因此我们还需要加载属性(properties)文件
name=itheima888
步骤2: 使用注解加载properties配置文件:在配置类上添加@PropertySource(properties文件名数组)
注解【这里不支持*.properties
】
@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
步骤3:使用@Value读取配置文件中的内容
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${name}")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
public class JdbcConfig {
@Bean//用来表示这个方法返回的是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({JdbcConfig.class})
@Configuration
//@ComponentScan("com.itheima.config")
@Import({JdbcConfig.class})
public class SpringConfig {
}
注意:扫描注解可以移除;@Import参数需要的是一个数组,可以引入多个配置类;@Import注解在配置类中只能写一次,下面的方式是不允许的
【问题】:对于下面代码关于数据库的四要素不应该写死在代码中,应该是从properties配置文件中读取。如何来优化下面的代码?
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;
}
}
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;
}
}
@ComponentScan("com.itheima.dao")
)扫描的目的是让Spring能管理到BookDao,也就是说要让IOC容器中有一个bookDao对象@Configuration
@ComponentScan("com.itheima.dao")
@Import({JdbcConfig.class})
public class SpringConfig {
}
在JdbcConfig类的方法上添加参数(下面的BookDao bookDao)
@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;
}
Spring有两个核心的概念,一个是IOC/DI,一个是AOP。,AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
作用:在不惊动原始设计的基础上为其进行功能增强,前面咱们有技术就可以实现这样的功能即代理模式,如何理解呢?见下面代码和说明:
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
//记录程序当前执行执行(开始时间)
Long startTime = System.currentTimeMillis();
//业务执行万次
for (int i = 0;i<10000;i++) {
System.out.println("book dao save ...");
}
//记录程序当前执行时间(结束时间)
Long endTime = System.currentTimeMillis();
//计算时间差
Long totalTime = endTime-startTime;
//输出信息
System.out.println("执行万次消耗时间:" + totalTime + "ms");
}
public void update(){
System.out.println("book dao update ...");
}
public void delete(){
System.out.println("book dao delete ...");
}
public void select(){
System.out.println("book dao select ...");
}
}
说明: AOP就是在不改变上边代码的同时也能使得updat、delete、select方法实现save中的代码功能,总体上说就是 在不惊动(改动)原有设计(代码)的前提下,想给谁(Spring一般指给方法添加)添加功能就给谁添加。
【概念说明】:
【环境配置】:
//添加BookDao和BookDaoImpl类
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 ...");
}
}
//创建Spring的配置类
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
//编写App运行类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.save();
bookDao.update();
}
}
说明:目前打印save方法的时候,因为方法中有打印系统时间,所以运行的时候是可以看到系统时间,对于update方法来说,就没有该功能,我们要使用SpringAOP的方式在不改变update方法的前提下让其具有打印系统时间的功能。
【步骤】:
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
说明:因为spring-context中已经导入了spring-aop ,所以不需要再单独导入spring-aop;导入AspectJ的jar包,AspectJ是AOP思想的一个具体实现,Spring有自己的AOP实现,但是相比于AspectJ来说比较麻烦,所以我们直接采用Spring整合ApsectJ的方式进行AOP开发。
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
public void method(){
System.out.println(System.currentTimeMillis());
}
}
说明:切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。execution及后面编写的内容,后面会有章节专门去学习。
制作切面,切面是用来描述通知和切入点之间的关系,如何进行关系的绑定?—>绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
说明:@Before翻译过来是之前,也就是说通知会在切入点方法执行之前执行,除此之前还有其他四种类型,后面会讲。
将通知类配给容器(@Component)并标识其为切面类(@Aspect)
@Component
@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());
}
}
@Configuration
@ComponentScan("com.itheima")
@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();
}
}
说明:看到在执行update方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功。
【流程1:Spring容器启动】:
【流程2:读取所有切面配置中的切入点】:下面这个例子中有两个切入点的配置,但是第一个ptx()并没有被使用,所以不会被读取。
【流程3:初始化bean】:
判定bean对应的类中的方法是否匹配到任意切入点
【切入点表达式】:动作关键字 (访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
对于AOP中切入点表达式,我们总共会学习三个内容,分别是语法格式、通配符和书写技巧。
【语法格式】:
对于切入点表达式的语法为:
切入点表达式标准格式:动作关键字 (访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
【通配符】:
【书写技巧】:
【通知类型的说明】:前面的案例中,@Before("pt()")
它所代表的含义是将通知(通知中有方法体)添加到切入点方法执行(方法体)的前面。这就是前置通知类型
【类型介绍】:共提供了5种通知类型—>不同的注解表示通知方法体加入到切入方法体的哪个位置
–上图是切入点的方法体
@Before("pt()")
,追加功能到方法执行前,类似于在代码1或者代码2添加内容@After("pt()")
,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容@Around("pt()")
,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能,具体是如何实现的,需要我们往学习。@AfterReturning("pt2()")
,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加@AfterThrowing,
追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加下面以环绕通知类型举例:
public interface BookDao {
public void update();
}
@Repository
public class BookDaoImpl implements BookDao {
public void update(){//切入点
System.out.println("book dao update ...");//切入点方法体
}
}
环绕型通知配置:其中原始操作(即是上面的System.out.println("book dao update ...");
被下面的pjp.proceed();
代替了)
@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 ...");
}
}
public interface BookDao {
public void select();
}
@Repository
public class BookDaoImpl implements BookDao {
public int select(){//切入点
System.out.println("book dao select ...");//切入点方法体
return 100;
}
}
其中通知的返回类型从void要改为Object(可以是int但Object更通用),并且返回值需要被ret接收
@Component
@Aspect
public class MyAdvice {
@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;
}
}
修改App类,调用select方法
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
int num = bookDao.select();
System.out.println(num);
}
}
【需求】:任意业务层接口执行均可以显示其执行效率(执行时长)
【分析】:
【环境以及目录结构】:
【代码(所在目录与上面标号匹配)以及注释说明】:
配置依赖:
具体代码:
//对应目录结构1:业务类实现接口,定义一些业务方法
public interface AccountService {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findById(Integer id);
}
//对应目录结构2:业务类实现类,实现业务方法
@Service //注释业务类bean
public class AccountServiceImpl implements AccountService {
@Autowired //自动注入AccountDao对象,实现具体的操作数据库方法
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();
}
}
//对应目录结构3:数据层实现接口,实现类通过注释来实现
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 class Account implements Serializable {
private Integer id;
private String name;
private Double money;
//setter..getter..toString方法省略
}
//jdc.properties属性文件,用来定义数据库连接配置属性
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
//Spring配置类:SpringConfig
@Configuration//配置类定义
@ComponentScan("com.itheima")//扫描
@PropertySource("classpath:jdbc.properties")//属性文件配置
@Import({JdbcConfig.class,MybatisConfig.class})//导入其他相关类
public class SpringConfig {
}
//JdbcConfig配置类
public class JdbcConfig {
@Value("${jdbc.driver}")//读入jsbc.properties文件的属性内容
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
//Druid数据源bean放入容器
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
//MybatisConfig配置类,SqlSessionFactoryBean、MapperScannerConfigurer配置
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
//测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)//注释配置测试对象配置文件
public class AccountServiceTestCase {
@Autowired//自动注入
private AccountService accountService;
@Test
public void testFindById(){
Account ac = accountService.findById(2);
}
@Test//测试方法注释
public void testFindAll(){
List<Account> all = accountService.findAll();
}
}
//AOP实现通知,切入点,切入面,通知类
@Component//bean对象
@Aspect//注释AOP
public class ProjectAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
//@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式
@Around("servicePt()")
public void runSpeed(ProceedingJoinPoint pjp){
//获取执行签名信息
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");
}
}
【环境配置】:切入点方法有形参id和password
//添加BookDao和BookDaoImpl类
public interface BookDao {
public String findName(int id);
}
@Repository
//切入点方法含有形参
public class BookDaoImpl implements BookDao {
public String findName(int id,String password) {
System.out.println("id:"+id);
return "itcast";
}
}
//编写App运行类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
String name = bookDao.findName(100,itheima);
System.out.println(name);
}
}
//通知(环绕通知和其他通知)方法获取切入点方法的参数数组
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Before("pt()")
public void before(JoinPoint jp) //定义参数JoinPiont jp
Object[] args = jp.getArgs();//获取切入点方法对象jp的参数(用getArgs()方法)
args[0]=666;//对第一个参数进行修改
System.out.println(Arrays.toString(args));//打印参数数组·1
System.out.println("before advice ..." );
}
}
- ProceedingJoinPoint: 适用于环绕通知
@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();//获取返回值
return ret;
}
//其他的略
}
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@AfterReturning(value = "pt()",returning = "ret")//让pt{}表示的切入点返回值传给ret
public void afterReturning(Object ret) {//获得返回值参数
System.out.println("afterReturning advice ..."+ret);
}
}
- 环绕通知:切入点方法返回值直接由pjp.proceed获取
@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 ret = pjp.proceed(args);
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);
}
}
- 环绕通知:这块比较简单,以前我们是抛出异常,现在只需要将异常捕获,就可以获取到原始方法的异常信息了,在catch方法中就可以获取到异常,至于获取到异常以后该如何处理,这个就和你的业务需求有关了。
@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;
}
}
【相关概念介绍】:转账业务会有两次数据层的调用,一次是加钱一次是减钱;把事务放在数据层,加钱和减钱就有两个事务;没办法保证加钱和减钱同时成功或者同时失败;这个时候就需要将事务放在业务层进行处理。
环境搭建:
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
);
insert into tbl_account values(1,'Tom',1000);`在这里插入代码片`
insert into tbl_account values(2,'Jerry',1000);
//根据表创建模型类
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
//setter...getter...toString...方法略
}
//创建Dao接口
public interface AccountDao {
@Update("update tbl_account set money = money + #{money} where name = #
{name}")
void inMoney(@Param("name") String name, @Param("money") Double money);
@Update("update tbl_account set money = money - #{money} where name = #
{name}")
void outMoney(@Param("name") String name, @Param("money") Double money);
}
//创建Service接口和实现类
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;
public void transfer(String out,String in ,Double money) {
accountDao.outMoney(out,money);
accountDao.inMoney(in,money);
}
}
//添加jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
//创建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;
}
}
//创建MybatisConfig配置类
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
//创建SpringConfig配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
//编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() throws IOException {
accountService.transfer("Tom","Jerry",100D);
}
}
步骤:
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 {
}
【事务回滚】:已经出错的事务回到原来正常的时候,即上面加钱的操作由于异常并未执行但减钱的操作已经执行了所以减钱的操作需要同步回退到原来正常的时候
【事务传播行为案例】:事务传播行为:事务协调员对事务管理员所携带事务的处理态度(事务协调员(添加日志信息)决定是否加入事务管理员与其他事务成为一个事务的态度,若设置为不加入,则该事务协调员称为一个单独的事务)。
见【SSM框架中-SpringMVC、Maven】
见【SSM框架下-SpringBoot、 MyBatisPlusPlus】