目录
4、通过BeanFactory实例化bean(基于第三种方式改良)(重要!)
当前书写代码存在的问题:业务层中要使用Dao层的实现类就需要new一个实现类对象,如果这时又来一个实现类对象就要修改业务层代码,有需要重新编译测试,代码耦合度极高
使用IoC控制反转将对象的创建权交给外部,不要再new一个对象
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-contextartifactId>
- <version>5.3.16version>
- dependency>
-
- <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();
- }
-
- }
由于在service中仍然new了dao的对象才能调用dao方法,没有实现真正解耦
-
- <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" />
-
- <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
-
-
-
- <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;
- }
-
- }
可以看到虽然表面创建了两个不同的Dao对象,但他们都指向同一个bean ,所以bean的作用范围默认为单例,因为如果是非单例,每使用一次这个bean都会再重新创建一次这个bean会使得容器中的bean越来越多
可以通过scope属性设置非单例
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>
- public class BookDaoImpl implements BookDao {
-
- public BookDaoImpl() {
- System.out.println("bookdaoimpl构造器被调用");
- }
-
- @Override
- public void save() {
- System.out.println("bookdao");
- }
- }
可以看到Spring对bean进行实例化会默认通过无参构造器创建bean对象,通过反射实现
- public class BookDaoFactory {
- //创建一个BookDao 工厂,提供创建BookDaoImpl的方法
- public static BookDao getBookDao(){
- System.out.println("factory start");
- return new BookDaoImpl();
- }
- }
-
- <bean id="bookDao" class="com.itheima.factory.BookDaoFactory" factory-method="getBookDao">bean>
- public class BookDaoFactory2 {
- //创建实例工厂
- public BookDao getBookDao(){
- System.out.println("实例工厂被调用");
- return new BookDaoImpl();
- }
- }
- <bean id="BookFactory" class="com.itheima.factory.BookDaoFactory2">bean>
-
- <bean id="bookDao" factory-method="getBookDao" factory-bean="BookFactory">bean>
- //实现FactoryBean接口,声明泛型类型,可能还有其他的bean需要实例化只需要修改泛型类型即可
- public class BookFactoryBean implements FactoryBean
{ -
- @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;
- }
- }
- <bean id="bookDao" class="com.itheima.factory.BookFactoryBean">bean>
上节bean的实例化都是使用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">
-
- <property name="maxLink" value="10"/>
- <property name="databaseName" value="uesr"/>
- bean>
- 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">bean>
-
- <bean id="userdao" class="com.itheima.dao.impl.UserDaoImpl">bean>
-
- <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
-
- <property name="bookDao" ref="bookdao">property>
- <property name="userDao" ref="userdao">property>
- bean>
- 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">
-
- <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">
-
- <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">
-
- <constructor-arg index="0" ref="bookdao"/>
- <constructor-arg index="1" ref="userdao"/>
- bean>
构造器注入是必须注入的,而setter注入是可选的可注可不注
-
- <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();
- }
- }
如果集合中要加入引用类型的数据将value改为ref
用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>
-
- <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" system-properties-mode="NEVER"/>
-
-
- <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、使用类路径创建容器
- ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- //2、使用文件系统绝对路径创建容器
- ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\2ProgramTool\\JavaEETest\\stage1\\springdemo3\\src\\main\\resources\\jdbc.properties");
ApplicationContext的祖宗接口就是BeanFactory
使用纯注解定义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);
- }
- }
注入引用类型
- @Repository("bookDao")
- public class BookDaoImpl implements BookDao {
-
- @Autowired //默认按类型自动装配,不需要写setter入口
- @Qualifier("mappDao") //按名称装配
- private Mapp mapp;
-
- public void save() {
- System.out.println("bookDao被创建");
- mapp.save();
- }
- }
注入基本类型
- @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 {
- //使用配置类代替配置文件
-
- }
直接设置参数,会进行自动装配
导入坐标
-
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-jdbcartifactId>
- <version>5.3.16version>
- dependency>
- <dependency>
- <groupId>org.mybatisgroupId>
- <artifactId>mybatis-springartifactId>
- <version>1.3.0version>
- 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 {
- }
导入坐标
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-testartifactId>
- <version>5.3.16version>
- dependency>
- <dependency>
- <groupId>junitgroupId>
- <artifactId>junitartifactId>
- <version>4.12version>
- <scope>testscope>
- 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));
- }
- }
导入需要的坐标
在导入spring-context时是默认自动导入aop的坐标
- <dependencies>
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-contextartifactId>
- <version>5.3.16version>
- dependency>
-
- <dependency>
- <groupId>org.aspectjgroupId>
- <artifactId>aspectjweaverartifactId>
- <version>1.9.4version>
- 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 {
- }
底层是代理模式增强功能
- @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;
- }
获取原始方法的参数数组,可以对参数进行修改,注意如果使用了JoinPoint作为参数一定要放在参数最前面。
- @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;
-
- }
- }