任务一:自定义IoC&AOP框架

一、Spring概述

1. Spring简介

Spring 是一个分层的轻量级的开源框架,以Ioc和Aop为内核,提供了展现层、业务层等众多的企业级应用技术,还能整合各种开源框架,已经成为使用最多的java EE 企业应用框架。
官方网站:http://spring.io
我们经常说的spring实际上指的是spring framework

2. Spring发展历程

  • 1997年提出EJB思想,1998年发布EJB1.0版本,直到2006年发布EJB3.0规范
  • Rod Johnson 在2004年发布了J2EE开发不使用EJB的解决方式,提出了Spring的雏形
  • 2017年9月Spring 发布了5.0的版本

3. Spring的优势

  • 方便解耦,简化开发
    通过Spring提供的Ioc容器,可以将对象之间的关系交由Spring进行控制,避免硬编码导致的国度耦合。同时用户也不需要为单例、配置文件解析等操作进行编码,只需要关注其上层的应用
  • Aop编程的支持
    通过Spring的Aop功能,方便对切面进行编程,可以解决传统OOP不易实现的功能
  • 声明式事务的支持
    可以将开发人员从烦闷的事务管理代码中解脱出来,通过声明方式灵活的进行事务的管理,提高开发效率
  • 方便程序的测试
    可以使用非容器依赖的方式进行几乎所有的测试工作,使测试变得随手可做
  • 可以集成各种优秀框架
    Spring可以降低其他框架的使用难度,提供了对很多框架的支持
  • 降低Java EE API的使用难度
    Spring对javaEE Api进行了封装,使用这些api的难度下降
  • 源码是经典的java学习范例
    Spring的代码设计精妙、结构清晰,处处体现着⼤师对Java设计模式灵活运⽤以及对
    Java技术的⾼深造诣。它的源代码⽆疑是Java技术的最佳实践的范例

3.Spring的核心结构

主要包括⼏个⼤模块:数据处理模块、 Web模块、 AOP(Aspect Oriented Programming) /Aspects模块、 Core Container模块和 Test 模块
在这里插入图片描述

  • Spring核⼼容器(Core Container) 容器是Spring框架最核⼼的部分,它管理着Spring应⽤中
    bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。基于bean⼯⼚,我们还会发现有多种Spring应⽤上下⽂的实现。所有的Spring模块都构建于核⼼容器之上。
  • ⾯向切⾯编程(AOP) /Aspects Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应⽤系统中开发切⾯的基础,与DI⼀样, AOP可以帮助应⽤对象解耦
  • 据访问与集成(Data Access/Integration)
    Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外, Spring AOP为数据访问提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、 MyBatis等。该模块由JDBC、 Transactions、 ORM、 OXM 和 JMS 等模块组成
  • Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平
  • Test 为了使得开发者能够很⽅便的进⾏测试, Spring提供了测试模块以致⼒于Spring应⽤的测试。 通过该模块, Spring为使⽤Servlet、 JNDI等编写单元测试提供了⼀系列的mock对象实现

4.Spring框架版本

Spring版本所需JDK版本
3.xJDK 5+
4.xJDK 6+
5.xJDK 8+

二、核心思想

1.Ioc

1.1 什么是Ioc

Ioc (Inversion of Control) 控制翻转,它是一种思想,不是一种技术实现。它描述的是对象的创建以及管理的问题。
传统开发中:A依赖于B,我们就会在A中new 一个B对象
Ioc模式下:我们不需要手动去创建对象,由Ioc容器代替我们去创建和管理对象。我们在需要的时候向容器要即可。
在这里插入图片描述

1.2 Ioc解决的什么问题

它解决的是对象之间的耦合问题。在传统中模式下,我们需要使用一个接口会new一个它的实现类,如果这个新增一个实现类,要在所有调用的地方都进行替换,耦合严重。
在这里插入图片描述

1.3 Ioc和DI的区别

DI : 依赖注入
DI和Ioc都是跟对象实例化和依赖关系维护相关,Ioc是站在对象的角度,说明对象的创建以及管理交由容器管理。DI是站在容器的角度,在创建对象时要把依赖的其他对象注入。

2.Aop

2.1 什么是Aop

Aop是面向切面编程,它是oop的延续,oop具有三大特性:封装、继承、多态,对于重复的代码,可以通过继承向上提取。所以oop是一种垂直继承体系。所以一般的代码重复问题oop都可以很好的解决,但是对于多个方法中相同位置出现了相同的代码,就无法解决。Aop可以通过横向的抽取机制

2.2 Aop解决的是什么问题

解决的是oop不能解决的代码重复问题,即在不改变原有业务逻辑的情况下,增强横切代码逻辑
,将业务代码与监控、日志等功能进行解耦,避免横切代码的重复。

2.3 为什么叫做面向切面编程

「切」:不改变原有逻辑,把横切逻辑 切入进入
「面」:切入的点一般很多,多个点形成一个面,如果只对一个地方进行切入,横向逻辑反而麻烦

三、手写实现Ioc和Aop

1.给定一个案例及关键代码

需求:A账户向B账户进行转账,数据存在数据库的表中
关键代码:
TransferService接口

public interface TransferService {
    /**
     * 定义一个 转账的方法   
     */
    void transfer(String fromCardNo, String toCardNo, Integer money) throws Exception;
}

TransferServiceImpl

public class TransferServiceImpl implements TransferService {

    private AccountDao accountDao = new AccountDaoImpl();

    @Override
    public void transfer(String fromCardNo, String toCardNo, Integer money) throws Exception {

        Account from = accountDao.queryAccountCardNo(fromCardNo);
        Account to = accountDao.queryAccountCardNo(toCardNo);

        from.setMoney(from.getMoney() - money);
        to.setMoney(to.getMoney() + money);

        accountDao.updateAccountByCardNo(from);
        accountDao.updateAccountByCardNo(to);
    }
}

AccountDao接口

public interface AccountDao {
    // 在接口中,定义一个 根据 cardNo 查询 账户的 方法
    Account queryAccountCardNo(String cardNo) throws SQLException, Exception;

    // 在接口中,定义一个 根据 cardNo 修改 money 的方法
    int updateAccountByCardNo(Account account) throws Exception;
}

JdbcAccountDaoImpl

public class AccountDaoImpl implements AccountDao {
    // 在接口 实现类中 实现根据 cardNo查询账户的方法
    @Override
    public Account queryAccountCardNo(String cardNo) throws Exception {

        DruidPooledConnection connection = DruidUtils.getInstance().getConnection();

        String sql = "select * from account where cardNo = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setString(1,cardNo);

        ResultSet resultSet = preparedStatement.executeQuery();

        Account account = new Account();

        while (resultSet.next()){

            account.setMoney(resultSet.getInt("money"));
            account.setCardNo(resultSet.getString("cardNo"));
            account.setName(resultSet.getString("name"));

        }

        resultSet.close();
        preparedStatement.close();
        connection.close();

        return  account;
    }

    // 在接口 实现类中 实现 根据 cardNo 修改 money 的方法
    @Override
    public int updateAccountByCardNo(Account account) throws Exception{

        DruidPooledConnection connection = DruidUtils.getInstance().getConnection();

        String sql = "update account set money = ? where cardNo = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setInt(1,account.getMoney());
        preparedStatement.setString(2,account.getCardNo());

        int i = preparedStatement.executeUpdate();

        preparedStatement.close();
        connection.close();

        return i;
    }
}

2.代码存在的问题分析

在这里插入图片描述

  • 问题一:service调用Dao的时候,是在实现类中直接new一个JdbcAccountDaoImpl,这个操作将这两个类耦合了在一起,如果后续使用其他Dao层进行数据库操作,将会对所有new的地方进行替换,成本比较大
  • 问题二:service在处理业务逻辑的时候没有进行事务管理,如果代码执行过程中发生错误,可能会导致数据库的数据错乱,问题很严重。

3.解决问题

对于问题一,我们可以使用反射来获取对象,通过把类的全路径配置在xml中,因为类会有很对,所以可以再工厂类中进行反射,并存储对象,工厂类还应该提供获取实例的方法。

  1. 建立beans.xml,在里面配置需要加载的类的全路径
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <!-- class 指定类的全路径,id用于获取实例对象时的唯一标识-->
    <bean id="accountDao" class="com.lagou.dao.Impl.AccountDaoImpl"/>
    <bean id="transferService" class = "com.lagou.service.Impl.TransferServiceImpl"/>
</beans>
  1. 创建BeanFactory类
package com.lagou.factory;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BeanFactory {

    //存储实例化的对象
    private static Map<String, Object> objectMap = new HashMap<>();

    static {
        //1.加载并解析xml配置
        InputStream resource = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resource);
            //获取根元素
            Element rootElement = document.getRootElement();
            //获取所有bean标签
            List<Element> elements = rootElement.selectNodes("//bean");
            elements.forEach(e -> {
                        String id = e.attributeValue("id");
                        String clazz = e.attributeValue("class");
                        //2.运用反射实例化对象
                        try {
                            Class<?> objectClass = Class.forName(clazz);
                            Object object = objectClass.newInstance();
                            objectMap.put(id, object);
                        } catch (Exception e1) {
                            e1.printStackTrace();
                        }
                    }
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
    * 获取bean方法
    */
    public static Object getBean(String id){
        return objectMap.get(id);
    }
}
  1. 修改原先的实现类
public class TransferServiceImpl implements TransferService {
   // private AccountDao accountDao = new AccountDaoImpl();
    private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
}

虽然通过反射实现了类的实例化,但是类中的属性仍然是人为的从beanFactory中获取,没有实现依赖自动的注入,而且还会因为类实例化的顺序,导致有依赖关系的类的属性为null。所以我们提出了方案二

  1. 修改beans.xml
	<!--    <bean id="transferService" class = "com.lagou.service.Impl.TransferServiceImpl"/>-->
    <bean id="transferService" class = "com.lagou.service.Impl.TransferServiceImpl">
        <property name="accountDao" ref ="accountDao"/>
    </bean>
  1. 修改beanFactory,在实例化对象后继续注入属性
            //注入依赖
            List<Element> property = rootElement.selectNodes("//property");
            property.forEach(p -> {
                String name = p.attributeValue("name");
                String ref = p.attributeValue("ref");
                //获取property标签的父bean ,然后获取实例化对象,利用反射调用set方法进行注入
                Element bean = p.getParent();
                String parentId = bean.attributeValue("id");
                Object parent = objectMap.get(parentId);
                Method[] methods = parent.getClass().getMethods();
                for (int i = 0; i < methods.length; i++) {
                    if(methods[i].getName().equalsIgnoreCase("set"+ name)){
                        try {
                            methods[i].invoke(parent,objectMap.get(ref));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                objectMap.put(parentId,parent);
            });
  1. 修改实现类
public class TransferServiceImpl implements TransferService {

   // private AccountDao accountDao = new AccountDaoImpl();

   // private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}

对于问题二,事务的管理,最终是通过connection的commit和rollback方式实现的,我们发现在dao层每次执行方法的时候,都是去获取一个新的连接,这必然导致service中的两次更新方法,不在同一个连接(不在同一个事务中),所以我们可以让当前线程获取到相同的连接。

  1. 创建ConnectionUtil工具类
package com.lagou.util;

import java.sql.Connection;
import java.sql.SQLException;

public class ConnectionUtil {
    //使用单例,如果每次new一个util,会导致threadLocal 也每次是新的,每次就都是第一次获取数据库连接
    private ConnectionUtil() {
    }

    private static ConnectionUtil connectionUtil = new ConnectionUtil();

    public static ConnectionUtil getInstance() {
        return connectionUtil;
    }

    //存储线程持有的connection
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public Connection getCurrentThreadConnect() throws SQLException {
        Connection connection = threadLocal.get();
        if (connection == null) {
            //第一次获取连接
            connection = DruidUtils.getInstance().getConnection();
            threadLocal.set(connection);
        }
        return connection;
    }
}
  1. 修改到层实现,修改connection获取方式,并去掉connection的关闭
   //Connection connection = DruidUtils.getInstance().getConnection();
     Connection connection = ConnectionUtil.getInstance().getCurrentThreadConnect();
     ......
   //connection.close();
  1. 创建事务工具类TransactionManager
package com.lagou.util;

import java.sql.SQLException;

public class TransactionManager {
    private TransactionManager(){

    }
    private static TransactionManager transactionManager = new TransactionManager();

    public static TransactionManager getInstance(){
        return transactionManager;
    }

    public void beginTransation() throws SQLException {
        ConnectionUtil.getInstance().getCurrentThreadConnect().setAutoCommit(false);
    }

    public void commit() throws SQLException {
        ConnectionUtil.getInstance().getCurrentThreadConnect().commit();
    }

    public void rollback() throws SQLException {
        ConnectionUtil.getInstance().getCurrentThreadConnect().rollback();
    }
}
  1. 修改service实现
@Override
    public void transfer(String fromCardNo, String toCardNo, Integer money) throws Exception {
        try {
            //开启事务
            TransactionManager.getInstance().beginTransation();
            Account from = accountDao.queryAccountCardNo(fromCardNo);
            Account to = accountDao.queryAccountCardNo(toCardNo);

            from.setMoney(from.getMoney() - money);
            to.setMoney(to.getMoney() + money);

            accountDao.updateAccountByCardNo(from);
            accountDao.updateAccountByCardNo(to);
            //提交事务
            TransactionManager.getInstance().commit();
        } catch (Exception e) {
            e.printStackTrace();
            //回滚事务
            TransactionManager.getInstance().rollback();
        }
    }

4. 回顾代理方式

代理一共分为两种类型,静态代理和动态代理,动态代理又有两种实现,jdk动态代理和cglib动态代理。

  1. 静态代理
  • 创建一个接口
package com.lagou.poxy;

public interface IRentingHouse {
    void rentHouse();
}
  • 创建实现类
package com.lagou.poxy;

public class RentingHouseImpl implements IRentingHouse{
    @Override
    public void rentHouse() {
        System.out.println("我要组一个房租");
    }
}
  • 创建一个静态代理类
package com.lagou.poxy.statics;

import com.lagou.poxy.IRentingHouse;
import com.lagou.poxy.RentingHouseImpl;

public class StaticRentingHouseProxy implements IRentingHouse {
    private IRentingHouse rentingHouse;
    public StaticRentingHouseProxy(IRentingHouse rentingHouse) {
        this.rentingHouse = rentingHouse;
    }

    @Override
    public void rentHouse() {
        System.out.println("房屋代理带你去看房子");
        rentingHouse.rentHouse();
        System.out.println("中介把你的信息卖了3毛钱");
    }
}
  • 测试
    public static void main(String[] args) {
        IRentingHouse myRentingHouse = new RentingHouseImpl();
        StaticRentingHouseProxy proxy = new StaticRentingHouseProxy(myRentingHouse);
        proxy.rentHouse();
    }
  1. JDK动态代理
    public static void main(String[] args) {
        IRentingHouse myRentingHouse = new RentingHouseImpl();
        IRentingHouse proxyRentingHouse = (IRentingHouse) Proxy.newProxyInstance(myRentingHouse.getClass().getClassLoader(), myRentingHouse.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("房屋代理带你去看房子");
                Object object = method.invoke(myRentingHouse, args);
                System.out.println("中介把你的信息卖了3毛钱");
                return object;
            }
        });
        proxyRentingHouse.rentHouse();
    }
  1. cglib动态代理
  • 引入jar包
        <!-- cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.6</version>
        </dependency>
  • 实现
    public static void main(String[] args) {
        RentingHouseImpl myRentingHouse = new RentingHouseImpl();

        RentingHouseImpl proxy = (RentingHouseImpl)  Enhancer.create(myRentingHouse.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("房屋代理带你去看房子");
                Object object = method.invoke(myRentingHouse, objects);
                System.out.println("中介把你的信息卖了3毛钱");
                return object;
            }
        });
        proxy.rentHouse();
    }
  1. 两种代理的比较
  • Cglib和jdk动态代理的区别?
1、Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
2、Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理
  • 什么时候用cglib什么时候用jdk动态代理?
1、如果目标对象实现了接口,既可以使用JDK也可以使用cglib
2、如果目标对象没有实现接口,只能使用cglib
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值