Spring IOC与AOP的简单实现

IOC

XML方式

  1. 将Spring的核心依赖包全部通过maven导入
<!--    spring-core spring-beans spring-context spring-expression-->
<!--    这几个包的版本必须一致-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
  </dependencies>
  1. 在resources文件夹下创建一个Spring config文件
    在这里插入图片描述
  2. 在java文件夹下面创建一个MVC的基本架构(domain、persistence、service、注意test是用来做单元检测的)
    在这里插入图片描述
    其中Account、AccountDAOImpl、AccountService的代码如下:
public class Account {

    private String userName;

    private String password;

    private int age;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Account(String userName, String password, int age) {
        this.userName = userName;
        this.password = password;
        this.age = age;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


}
public class AccountDAOImpl implements  AccountDAO {
    @Override
    public void insert() {
        System.out.println("新增用户");
    }

    @Override
    public void deletd() {
        System.out.println("删除用户");
    }

    @Override
    public void update() {
        System.out.println("更新用户");
    }

    @Override
    public void select() {
        System.out.println("查询用户");
    }
}
public class AccountService {

    private AccountDAO accountDAO;

    public AccountService(AccountDAO accountDAO) {
        this.accountDAO = accountDAO;
    }

    public void login(){
        System.out.println("Service的login方法");
        accountDAO.select();
    }

    public AccountDAO getAccountDAO() {
        return accountDAO;
    }

    public void setAccountDAO(AccountDAO accountDAO) {
        this.accountDAO = accountDAO;
    }
}
  1. 根据Account、AccountDAOImpl、AccountService的调用关系(例如Service调用Dao层来实现Account的查询等操作),配置相应的Spring config文件,实现IOC的XML注入方式,注意这里有两种注入方式,一种是构造函数注入,一种是属性注入。
    属性注入:
<!--     传统的配置注入方式 -->
        <bean id="accountDao" class="org.csu.spring.demo.ioc.persistence.AccountDAOImpl"/>
        <bean id="account" class="org.csu.spring.demo.ioc.demain.Account">
<!--            <property name="userName" value="John"/>-->
<!--            <property name="password" value="123456"/>-->
<!--            <property name="age" value="18"/>-->
        </bean>
        <bean id="accountService" class="org.csu.spring.demo.ioc.service.AccountService">
            <property name="accountDAO" ref="accountDao"/>
        </bean>

构造函数注入:

<!--     传统的配置注入方式 -->
        <bean id="accountDao" class="org.csu.spring.demo.ioc.persistence.AccountDAOImpl"/>
        <bean id="account" class="org.csu.spring.demo.ioc.demain.Account">
            <constructor-arg name="userName" value="John"/>
            <constructor-arg name="password" value="666666"/>
            <constructor-arg name="age" value="18"/>
        </bean>
        <bean id="accountService" class="org.csu.spring.demo.ioc.service.AccountService">
            <constructor-arg name="accountDAO" ref="accountDao"/>
        </bean>
  1. 在test文件夹下创建一个demo单元测试单元,进行测试
public class demo {

    private AccountService service;

    @Test
    public  void test(){
        //使用Spring Ioc容器来获取对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Account temp = (Account)context.getBean("account") ;
        AccountService service = (AccountService)context.getBean("accountService");
        service.login();
        System.out.println(temp.getUserName() + "," + temp.getPassword() + "," + String.valueOf(temp.getAge()));

    }

}

测试结果如下:
在这里插入图片描述
6. 总结
此方法为Spring IOC实现的基础方法-基于XML配置文件实现的。这样的方法虽然可以实现IOC控制反转,但是有明显的不足。如果有一百个类似的MVC结构的类关系,难道你需要在配置文件配置一百次吗?显然这个是不人性化的,所以接下来介绍注解方式实现IOC。

注解方式

注意:前面的1、2、3步和基于XML配置文件实现IOC的步骤一模一样,这里直接从第四步开始!

  1. 将之前的Spring conf配置文件的bean全部删掉,加上下面这一个语句,注释功能实现的基础就是这个包扫描语句
<!--     开启注解的包扫描-->
    <context:component-scan base-package="org.csu.spring.demo.ioc"/>
  1. 在MVC框架中的各个类里面加上相应的注释

@Component(“name”)用于实体类的声明

@Component("account")
public class Account {
    @Value("Mike")
    private String userName;
    @Value("888888")
    private String password;
    @Value("22")
    private int age;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
    

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


}

@Repository(“name”)用于Dao类的声明

@Repository("accountDao")
public class AccountDAOImpl implements  AccountDAO {
    @Override
    public void insert() {
        System.out.println("新增用户");
    }

    @Override
    public void deletd() {
        System.out.println("删除用户");
    }

    @Override
    public void update() {
        System.out.println("更新用户");
    }

    @Override
    public void select() {
        System.out.println("查询用户");
    }
}

@Service(“name”)用于业务类的声明

@Service("accountService")
public class AccountService {

    //这个注解它会扫描ioc包的内容,根据类型匹配来注入,如果找不到,会注入null值
    @Autowired
    //按名称去制定
    @Resource(name = "accountDao")
    private AccountDAO accountDAO;

    public AccountService(AccountDAO accountDAO) {
        this.accountDAO = accountDAO;
    }

    public void login(){
        System.out.println("Service的login方法");
        accountDAO.select();
    }

    public AccountDAO getAccountDAO() {
        return accountDAO;
    }

    public void setAccountDAO(AccountDAO accountDAO) {
        this.accountDAO = accountDAO;
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“classpath:applicationContext.xml”)
代表JUnit在相应的环境下进行测试

//用Junit创建环境
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class demo {

    @Autowired
    private AccountService service;


    @Test
    public  void test(){
        service.login();
    }

}
  1. 运行demo1的单元测试,结果如下
    在这里插入图片描述
  2. 总结
    注解的方式只是减少了程序员编码的工作,其中的原理和之前介绍的用配置XML方式实现IOC注入的原理是一模一样的

AOP

传统Spring AOP

动态代理代理类实现

  1. 建立如下的目录结构(aspectj在后面会介绍,这里暂时不用)
    在这里插入图片描述
  2. 相应类的代码
    AccountDao与AccountImpl两个类和之前IOC里面的一模一样,直接拷贝即可。
public class proxyDemo implements InvocationHandler {

    private Object object;

    public proxyDemo(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("select"))
        beforeAdvice();
        Object proxyObject = method.invoke(object,args);
        return proxyObject;
    }

    public void beforeAdvice(){
        System.out.println("前置建议:日志记录。。。");
    }
}
public class demo {

//    @Autowired
//    private AccountDAO accountDAO;


    @Test
    public void test(){

        AccountDAO accountDao = new AccountDAOImpl();

        Class class1 = accountDao.getClass();

        accountDao = (AccountDAO)Proxy.newProxyInstance(class1.getClassLoader(),class1.getInterfaces(),new proxyDemo(accountDao));

        accountDao.insert();

        accountDao.select();

    }
}
  1. 运行demo单元测试,结果如下
    在这里插入图片描述
  2. 总结
    传统AOP就是基于InvocationHandler这个接口实现的,其思想和JVM类似。实现AOP的原理如下:通过一个类实现InvocationHandler接口来编写自己的代理类,代理类通过invoke方法实现,对所有被代理类的方法的切入。这个方法有许多不足:所有被代理的类必须是实现接口的类,而且代理类只能实现对于被代理类的方法的切入。

XML配置实现

  1. 在Resources下面添加新的Spring config文件,并且进行相应的配置(因为需要用到aop的xml命名空间,这里需要在Spring官网里面进行相应的搜索)
    在这里插入图片描述
  2. 新增一个Proxy类,为了和之前的代理类区分
public class SpringAopProxyDemo implements AfterReturningAdvice {


    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("后置意见。。。");
    }
}
  1. 配置Spring config文件
<?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:aop="http://www.springframework.org/schema/aop"
       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
       https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="accountDAO" class="org.csu.spring.demo.aop.persistence.AccountDAOImpl"/>

    <bean id="demoProxy" class="org.csu.spring.demo.aop.proxy.SpringAopProxyDemo"/>

    <bean id="demoAfterProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="accountDAO"/>
        <property name="interceptorNames" value="demoProxy"/>
        <property name="proxyInterfaces" value="org.csu.spring.demo.aop.persistence.AccountDAO"/>
    </bean>
</beans>
  1. 修改demo测试单元的内容,并且运行得出结果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-aop.xml")
public class demo {

    @Resource(name="demoAfterProxy")
    private AccountDAO accountDAO;


    @Test
    public void test(){
        accountDAO.insert();

    }
}

在这里插入图片描述

  1. 结论
    这个方式和之前的基于动态代理的实现原理一模一样,只不过把硬编码的方式编程类配置文件的方式。

基于AspectJ的Spring AOP

  1. 修改Spring config的内容
<?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:aop="http://www.springframework.org/schema/aop"
       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
       https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <aop:aspectj-autoproxy/>
    <context:component-scan base-package="org.csu.spring.demo.aop"/>
    <bean id="demoAspect" class="org.csu.spring.demo.aop.persistence.demoAspect"/>
</beans>
  1. 在aspectj文件夹下面创建一个切面类
@Component
@Aspect
public class demoAspect {

    //execution(访问修饰符,返回类型,类,方法,参数)
    @Before(value = "execution(* org.csu.spring.demo.aop.persistence.*.*(..))")
    public void before(){
        System.out.println("这是一个前置建议:安全验证。。。");
    }
}
  1. 修改demo测试单元代码,执行得出结果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-aop.xml")
public class demo {

    @Autowired
    private AccountDAO accountDAO;


    @Test
    public void test(){

        accountDAO.select();

    }
}

在这里插入图片描述

  1. 结论
    aspectj的思想和之前传统的思想不太一样。动态代理以一个代理类为核心,所以每一个不同的被代理类都需要对应一个代理类,导致过程很繁琐;但是aspectj的核心为切面,每一个切面可以有对应多个被代理类的多个方法,这样的思想可以简化很多代码量!
posted @ 2020-03-15 13:51  南孚先生  阅读(347)  评论(0编辑  收藏  举报