目录
4、通过BeanFactory实例化bean(基于第三种方式改良)(重要!)
一、Spring系统架构
二、核心概念
当前书写代码存在的问题:业务层中要使用Dao层的实现类就需要new一个实现类对象,如果这时又来一个实现类对象就要修改业务层代码,有需要重新编译测试,代码耦合度极高
使用IoC控制反转将对象的创建权交给外部,不要再new一个对象
三、IoC入门案例
<!--1、导入spring坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.16</version>
</dependency>
<!--1、导入依赖坐标-->
<!--2、创建Bean 存放实现类对象的全限定名-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" />
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
</bean>
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
@Override
public void save() {
System.out.println("bookservice");
bookDao.save();
}
}
四、DI入门案例
由于在service中仍然new了dao的对象才能调用dao方法,没有实现真正解耦
<!--1、导入依赖坐标-->
<!--2、创建Bean 存放实现类对象的全限定名-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" />
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--3、关系绑定,依赖注入-->
<!--service中需要创建dao的对象,所以绑定在service-->
<!--
property配置当前bean的属性
name 表示service层定义的对象名 private BookDao bookDao;
ref 表示service层定义的对象名对应的bean对象名 id值
-->
<property name="bookDao" ref="bookDao"></property>
</bean>
public class BookServiceImpl implements BookService {
//仍然new了实现类对象才能进行数据交互
//private BookDao bookDao = new BookDaoImpl();
//1、删除new的实现类对象
private BookDao bookDao;
@Override
public void save() {
System.out.println("bookservice");
bookDao.save();
}
//2、创建setBookDao方法
public void setBookDao(BookDao bookDao){
this.bookDao = bookDao;
}
}
五、bean (使用XML配置bean)
1、基础配置
2、bean别名配置
3、bean作用范围
可以看到虽然表面创建了两个不同的Dao对象,但他们都指向同一个bean ,所以bean的作用范围默认为单例,因为如果是非单例,每使用一次这个bean都会再重新创建一次这个bean会使得容器中的bean越来越多
可以通过scope属性设置非单例
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>
4、bean的实例化
1、通过构造器实例化bean
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("bookdaoimpl构造器被调用");
}
@Override
public void save() {
System.out.println("bookdao");
}
}
可以看到Spring对bean进行实例化会默认通过无参构造器创建bean对象,通过反射实现
2、通过静态工厂实例化bean
public class BookDaoFactory {
//创建一个BookDao 工厂,提供创建BookDaoImpl的方法
public static BookDao getBookDao(){
System.out.println("factory start");
return new BookDaoImpl();
}
}
<!--方式二:使用静态工厂实例化bean
提供工厂路径,注明调用工厂的哪一个方法-->
<bean id="bookDao" class="com.itheima.factory.BookDaoFactory" factory-method="getBookDao"></bean>
3、通过实例工厂实例化bean
public class BookDaoFactory2 {
//创建实例工厂
public BookDao getBookDao(){
System.out.println("实例工厂被调用");
return new BookDaoImpl();
}
}
<!--方式三:使用实例工厂实例化bean
先创建工厂的bean对象-->
<bean id="BookFactory" class="com.itheima.factory.BookDaoFactory2"></bean>
<!--再通过工厂创建具体的bean-->
<bean id="bookDao" factory-method="getBookDao" factory-bean="BookFactory"></bean>
4、通过BeanFactory实例化bean(基于第三种方式改良)(重要!)
//实现FactoryBean接口,声明泛型类型,可能还有其他的bean需要实例化只需要修改泛型类型即可
public class BookFactoryBean implements FactoryBean<BookDao> {
@Override
public BookDao getObject() throws Exception {
System.out.println("factorybean 启动");
//返回BookDao的实现类
return new BookDaoImpl();
}
@Override
public Class<?> getObjectType() {
//声明返回对象的类型
return BookDao.class;
}
@Override
public boolean isSingleton() {
//此处为true说明实例化的对象为单例(同一个bean),false为非单例
return true;
}
}
<!--方式四-->
<!--实际上是factory工厂通过方法创造的bean对象-->
<bean id="bookDao" class="com.itheima.factory.BookFactoryBean"></bean>
5、bean的生命周期
六、依赖注入
1、依赖注入的方式
1、setter注入
上节bean的实例化都是使用setter注入的方式进行
-
使用setter注入基本类型
public class BookServiceImpl2 implements BookService {
//使用setter注入 注入基本类型
private int maxLink;
private String databaseName;
public void setMaxLink(int maxLink) {
this.maxLink = maxLink;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
public void save() {
System.out.println("bookservice被创建");
System.out.println(maxLink +"..."+ databaseName);
}
}
<bean id="bookService2" class="com.itheima.service.impl.BookServiceImpl2">
<!--使用value设置基本类型的值-->
<property name="maxLink" value="10"/>
<property name="databaseName" value="uesr"/>
</bean>
-
使用setter注入引用类型
public class BookServiceImpl implements BookService {
private BookDao bookDao;
private UserDao userDao;
//创建set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save() {
System.out.println("bookservice被创建");
bookDao.save();
userDao.save();
}
}
<!--<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>-->
<bean id="bookdao" class="com.itheima.dao.impl.BookDaoImpl"></bean>
<bean id="userdao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--设置setter注入-->
<property name="bookDao" ref="bookdao"></property>
<property name="userDao" ref="userdao"></property>
</bean>
2、构造器注入
-
使用构造器注入引用类型
public class BookServiceImpl3 implements BookService {
//使用setter注入 注入基本类型
private BookDao bookDao1;
private UserDao userDao1;
//使用构造器注入,设置构造器参数
public BookServiceImpl3(BookDao bookDao1,UserDao userDao1) {
this.bookDao1 = bookDao1;
this.userDao1 = userDao1;
}
public void save() {
System.out.println("bookservice被创建");
bookDao1.save();
userDao1.save();
}
}
<bean id="bookService3" class="com.itheima.service.impl.BookServiceImpl3">
<!--name中是形参的名称 ref是对应bean的id-->
<constructor-arg name="bookDao1" ref="bookdao"/>
<constructor-arg name="userDao1" ref="userdao"/>
</bean>
-
使用构造器注入基本类型
仅仅修改配置文件中的ref 化成value
但是当前构造器注入仍然存在紧耦合的问题,参数名一旦发生改变配置文件就要相对应的改变
所以Spring给出了解决方案
<bean id="bookService3" class="com.itheima.service.impl.BookServiceImpl3">
<!--不使用名称name绑定,而是使用参数的类型进行绑定-->
<constructor-arg type="com.itheima.dao.BookDao" ref="bookdao"/>
<constructor-arg type="com.itheima.dao.UserDao" ref="userdao"/>
</bean>
但是使用这种方式也可能会出现参数类型相同的情况所以又引出了第二种解决方案
<bean id="bookService3" class="com.itheima.service.impl.BookServiceImpl3">
<!--使用index索引根据参数位置匹配,解决了参数类型重复问题-->
<constructor-arg index="0" ref="bookdao"/>
<constructor-arg index="1" ref="userdao"/>
</bean>
2、依赖注入的方式选择
构造器注入是必须注入的,而setter注入是可选的可注可不注
3、依赖自动装配
1、使用类型(byType)自动装配
2、使用名称(byName)自动装配
<!--使用自动装配-->
<!--通过名称自动装配 这里要使入口的set方法名称与对应bean的id相同
例如setBookDao 去set小写,bookDao,这个名称要与bookDao的bean的id相同
-->
<bean id="bookService4" class="com.itheima.service.impl.BookServiceImpl4" autowire="byName"/>
public class BookServiceImpl4 implements BookService {
//使用setter注入 注入基本类型
private BookDao bookDao;
private UserDao userDao;
//使用setter方法设置自动装配入口
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save() {
System.out.println("bookservice被创建");
bookDao.save();
userDao.save();
}
}
4、集合注入
如果集合中要加入引用类型的数据将value改为ref
5、管理数据源对象
用IoC容器来管理外部导入的资源,例如druid数据库连接池,junit测试等依赖
<!--配置外部数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--需要配置的属性-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306"/>
<property name="username" value="123"/>
<property name="password" value="123"/>
</bean>
6、Spring加载properties文件
<!--1、开启一个全新的命名空间-->
<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">
<!--2、使用新的命名空间引入properties文件-->
<!--system-properties-mode="NEVER"从不加载系统属性,防止文件名称与系统名称冲突-->
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
<!--3、使用占位符获取文件中的数据-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
七、容器
1、创建容器
//1、使用类路径创建容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、使用文件系统绝对路径创建容器
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\2ProgramTool\\JavaEETest\\stage1\\springdemo3\\src\\main\\resources\\jdbc.properties");
2、获取bean
3、ApplicationContext实现类
ApplicationContext的祖宗接口就是BeanFactory
八、核心容器、bean与依赖注入总结
九、注解开发
1、注解开发定义bean
2、纯注解开发
使用纯注解定义bean
//1、创建配置类代替配置文件
@Configuration //代表配置文件
@ComponentScan("com.itheima") //代表包扫描组件
@ComponentScan({"com.itheima","com.ittest"}) //在多个包扫描组件
public class SpringConfig {
//使用配置类代替配置文件
}
//2、定义bean
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("注解开发");
}
}
//3、使用bean
public class App {
public static void main(String[] args) {
//使用纯注解开发将不再需要applicatinContext文件
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//通过组件名称
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
System.out.println(bookDao);
}
}
3、注解中bean的管理
1、bean的作用范围
2、bean的生命周期
4、使用注解依赖注入
1、自动装配
注入引用类型
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Autowired //默认按类型自动装配,不需要写setter入口
@Qualifier("mappDao") //按名称装配
private Mapp mapp;
public void save() {
System.out.println("bookDao被创建");
mapp.save();
}
}
注入基本类型
2、注解加载外部文件
5、注解中第三方bean的管理
@Configuration //代表配置文件
@ComponentScan("com.itheima") //代表扫描组件
@PropertySource({"jdbc.properties","jdbc2.properties","jdbc3.properties","jdbc2-1.properties"})
public class SpringConfig {
//使用配置类代替配置文件
@Bean //说明返回类型是一个bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setName("root");
ds.setPassword("root");
return ds;
}
}
在实际开发中,外部的bean一般另外创建一个类来创建bean,然后导入的方式加到配置文件中
//1、单独创建一个类
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.setName("root");
ds.setPassword("root");
return ds;
}
}
@Configuration //代表配置文件
@Import(JdbcConfig.class) //导入外部bean 多个外部bean使用数组形式
public class SpringConfig {
//使用配置类代替配置文件
}
6、对外部bean依赖注入
1、注入简单类型
2、注入引用类型
直接设置参数,会进行自动装配
7、注解开发总结
十、Sping整合其他框架
1、Spring整合MyBatis
导入坐标
<!--spring整合mybatis需要使用的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
创建jdbc.properties外部文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/users?useSSL=false
username=root
password=12345
设置数据源 jdbc连接数据库
public class JdbcConfig {
@Value("${driver}")
private String driverClassName;
@Value("${url}")
private String url;
@Value("${name}")
private String name;
@Value("${password}")
private String password;
@Bean
public DataSource dataSource(){
//创建jdbc的数据库连接池DataSource
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driverClassName);
ds.setUrl(url);
ds.setName(name);
ds.setPassword(password);
return ds;
}
}
配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
public class SpringConfig {
}
2、Sping整合Junit
导入坐标
<!--spring整合junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
使用专用类运行器RunWith
@RunWith(SpringJUnit4ClassRunner.class)
//与配置类接通
@ContextConfiguration(classes = SpringConfig.class)
public class ServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testSelectById(){
System.out.println(accountService.selectById(2));
}
}
十一、AOP
1、AOP入门案例
导入需要的坐标
在导入spring-context时是默认自动导入aop的坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
//6、创建bean
@Component
//7、将这个bean作为AOP处理
@Aspect
public class MyAdvice {
//2、创建通知类 定义通知方法
//4、定义切入点
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
//5、设置切入点的位置,作为切面绑定通知与切入点的关系
@Before("pt()")
public void advice(){
//3、共性方法 通知
System.out.println(System.currentTimeMillis());
}
}
@Configuration
@ComponentScan("com.itheima")
//8、启动Aspect注解,开启AOP代理自动配置
@EnableAspectJAutoProxy
public class SpringConfig {
}
2、AOP工作流程
底层是代理模式增强功能
3、AOP切入点表达式
1、切入点表达式规则
2、切入点表达式通配符
3、书写技巧
4、AOP通知类型(切面注解)
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//1、前置通知
// @Before("pt()")
public void advice(){
System.out.println("before is running...");
}
//2、后置通知
// @After("pt()")
public void after(){
System.out.println("after is running...");
}
//3、环绕通知
@Around("pt2()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("before running...");
//调用原始方法
//Object proceed = joinPoint.proceed();
//还要将原始方法的返回值返回出去
Integer proceed = (Integer) joinPoint.proceed();
System.out.println("after running...");
//还可以对原始方法的返回值进行修改
return proceed;
}
5、AOP通知获取数据
获取原始方法的参数数组,可以对参数进行修改,注意如果使用了JoinPoint作为参数一定要放在参数最前面。
6、AOP案例:百度网盘分享链接输入密码AOP处理
@Component
@Aspect
public class BaiduAop {
@Pointcut("execution(boolean com.itheima.service.*Service.judgement(*,*))")
public void pt(){}
@Around("BaiduAop.pt()")
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
//获取参数数据
Object[] args = pjp.getArgs();
//对参数数组遍历,对字符串类型的参数进行trim处理
for (int i = 0; i < args.length; i++) {
if (args[i].getClass().equals(String.class)){
//如果是字符串就处理并修改参数
args[i] = args[i].toString().trim();
}
}
//将修改后的参数再传递给原始函数
Object proceed = pjp.proceed(args);
return proceed;
}
}
7、AOP总结
十二、Spring事务
Spring事务角色