ssh每日一篇Java

Spring(二)-Spring AOP

2018-08-24  本文已影响45人  sixleaves

1. Spring AOP

回顾

Spring(一)中,我分享了

今天分享啥

今天要分享的主要是关于Spring AOP, 即Spring的面向切面编程。再聊Spring AOP之前, 我会先分享一下AOP思想、由AOP思想我们再来聊聊如何实现AOP,也就是实现AOP的三种方式。了解完AOP思想和其实现,我们再来聊实际开发中我们如何使用Spring实现AOP,即Spring中实现AOP的方式。下面列下这片博客要分享的内容。

上一篇博客中,主要是使用xml进行转交控制权,告诉IoC容器如何注入
还有一种基于注解的方式进行DI,也是现在开发中最常用的方式,所以我们先来了解下DI相关-基于注解的自动装配

1.1 DI相关-基于注解的自动装配

首先我们要知道基于注解的自动装配其实是一种依赖注入。依赖注入我们即可以使用xml配置文件方式来告诉容器,如何去注入。也可以通过更简便的注解方式来告诉IoC容器,如何取注入。

1.1.1 如何使用注解转交控制权并告诉容器如何注入

关于这个问题,我们需要拆分成两个子问题。

告诉Spring IoC容器去哪些包下扫描注解

<?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: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-4.0.xsd">

    <context:component-scan base-package="com.sweetcs._09autowise_with_annotation"></context:component-scan>
    
</beans>

给要转交控制权的类打上标记

既然要大标记,在Java中即可以使用注解技术实现.Spring中根据J2EE的分层开发规范,提供了四个注解用来表示类对应的层,而且一旦被打上注解,意味着这些类一旦被Spring IoC容器扫描到,Spring IoC容器就会接管这它们

四个转交控制权注解

这四个注解的共同作用,都是我们用来将转交控制权给Spring IoC容器。不同的是他们的含义(语义), 不同的名称用来标注不同层的组件, @Component可以用来标注所有层的组件。

@Component注解

四个注解中的第一个是Componet注解,表示组件的意思, 所以对于daoservice等web组件都可以打上@Component注解。
表示这是一个组件的意思,并且将会托管给Spring IoC容器。

@Repository注解

该注解表示的是 数据仓库的意思,主要表示DAO层的对象。

@Controller注解

该注解表示的是控制器。例如Servlet就是控制器,其主要用来解析请求,处理请求,跳转。

@Service注解

该注解表示的是Service即业务逻辑层。

案例

下图为案例的项目代码结构图

<?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: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-4.0.xsd">

    <context:component-scan base-package="com.sweetcs._09autowise_with_annotation"></context:component-scan>
    
</beans>
@Repository
public class UserDAOImpl implements IUserDAO {

    @Override
    public void save() {
        // TODO Auto-generated method stub
        System.out.println("保存User对象到数据库");
    }
}

UserServiceImpl.业务逻辑层,故而打上@Service

@Service
@ToString
public class UserServiceImpl implements IUserService {

    @Autowired
    @Setter
    private IUserDAO userDAO;
    
    @Override
    public void save() {
        // TODO Auto-generated method stub
        userDAO.save();
    }
}

测试代码
告诉容器我们要什么对象,让它返回,我们测试十分容器给我们完成了注入。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AutoWiseWithAnnotationTest {
    
    @Autowired
    ApplicationContext ctx;
    
    @Test
    public void testDIWithAnnotation() {
        
        UserServiceImpl userServiceImpl = ctx.getBean("userServiceImpl", UserServiceImpl.class);
        userServiceImpl.save();
    }
}

输出

可以看到我们使用注解的方式转交控制权是有效的,我们可以在测试用例中成功的通过Spring IoC获取到对应的对象,而不是我们自己创建的。

关于@Autowired
如果没有看过Spring(一)你可能并不了解这个注解的含义.这个注解等效于xml配置响应的bean的autowire属性.是用来告诉容器,我们需要IoC容器给我们注入一个对象
至于是什么对象,它遵循以下算法.

接下去我们要来分享今天的重头戏了,AOP!!!

1.2 AOP

我们在Spring(一)中的简介Spring中有提到Spring基于IoCAOP思想从而实现了解耦。
那么什么是AOP?AOP(Aspect Oriented Programming)的中文意思是面向切面编程.这是一种很专业的称呼,我们先来看看AOP的思想。

AOP思想

我们先来看看维基百科对AOP的定义

In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a "pointcut" specification, such as "log all function calls when the function's name begins with 'set'". This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code, core to the functionality. AOP forms a basis for aspect-oriented software development.

上述中有几个关键术语

advice是AOP范式编程中的术语,其是要来描述某些类中的方法, 这些方法是用来修改其他类的定义的方法功能。这些方法会在连接点处被加入, 从而实现方法增强

所谓的切入点就是我们要切入的是哪些类.

基于维基百科的介绍, AOP的思想分离程序中交叉在一起的关注点,而每个关注点对应可以说是一段功能,AOP的想法就是将每个关注点模块化,使得他们独立无不干扰。从而具备职责分明可插拔互不干扰等特点。因为具备这些特点,所以能让代码具备很强的拓展性维护性.(因为可插拔,要增加一项功能,我们就能将其模块化, 插入原来的程序.不要的时候可以直接删除,对程序没有任何影响).

我们可以用图来更加形象的说明AOP,如下图。

AOP的三种实现方式

基于AOP的想法,在Java中我们有主要的三种方式可以来实现AOP.

1.装饰者模式

装饰者模式的思想:
不更改原有类的代码,动态的扩展一个对象的功能, 也就是对对象进行功能争强。
为什么装饰者模式能实现AOP:
通过对装饰者模式,我们也可以对将我们的业务逻辑代码和其他非业务逻辑代码做分离, 让装饰者作为代理来负责对应的功能, 而分离出去的代码如何修改争强,我们都不用去懂我们的业务逻辑代码,这正好符合我们AOP想要的。

说的太抽象,我们直接看具体的场景和代码.

假设我们现在有需要设计一个EmployeeServiceWrapper即EmployeeService的一个包装类,对它做功能上的争强,增加处理事务的能力

使用装饰者模式实现的EmpoyeeServiceWrapper

import lombok.Setter;

@Setter
public class EmployeeServiceWrapper implements IEmployeeService {

    // 被争强的真是对象
    private IEmployeeService target;
    // 提供管理事务的对象.
    private TxManager tx;
    
    
    
    
    @Override
    public void save() {
        
        try {
            tx.open();
            target.save();
            tx.commit();
        } catch (Exception e) {
            // TODO: handle exception
            tx.rollBack();
        }
    }

    @Override
    public void update() {
        
        try {
            tx.open();
            target.update();
            tx.commit();
        } catch (Exception e) {
            // TODO: handle exception
            tx.rollBack();
        }

    }
    public EmployeeServiceWrapper(IEmployeeService target, TxManager tx) {
        super();
        this.target = target;
        this.tx = tx;
    }
}

被代理类EmployeeServiceImpl

@Setter
public class EmployeeServiceImpl implements IEmployeeService {
    
    
    
    public IEmployeeDAO getEmployeeDAO() {
        return employeeDAO;
    }

    public void setEmployeeDAO(IEmployeeDAO employeeDAO) {
        this.employeeDAO = employeeDAO;
    }

    private IEmployeeDAO employeeDAO;
    
    @Override
    public void save() {
        employeeDAO.save();
        // TODO Auto-generated method stub
        
    }

    @Override
    public void update() {
        employeeDAO.update();
        // TODO Auto-generated method stub
        
    }   
}

模拟事务管理的类TxManager

public class TxManager {

    public void open() {
        // TODO Auto-generated method stub
        System.out.println("开启事务");
    }

    public void commit() {
        // TODO Auto-generated method stub
        System.out.println("提交事务");
    }

    public void rollBack() {
        // TODO Auto-generated method stub
        System.out.println("回滚事务");
    }

}

测试代码, EmployeeServiceWrapperTest

public class EmployeeServiceWrapperTest {
    
    @Test
    public void testEmplyeeServiceWrapper() {
        
        IEmployeeDAO employeeDAO = new EmployeeDAOImpl();
        IEmployeeService employeeService = new EmployeeServiceImpl();
        ((EmployeeServiceImpl)employeeService).setEmployeeDAO(employeeDAO);
        
        TxManager tx = new TxManager();
        IEmployeeService wrapper = new EmployeeServiceWrapper(employeeService, tx);
        
        wrapper.save();
        System.out.println();
        wrapper.update();
    }
}

输出

可见包装类并没有改变原有真实类的功能,还提供了事务管理功能.所以使用这种方式,我们也实现了AOP,但是这种AOP还是十分不灵活.

基于装饰者模式实现AOP的缺陷

2.静态代理

静态代理中真实角色必须是实现已经存在的,并将其作为代理对象的内部属性.

StaticProxyEmployeeService

public class StaticProxyEmployeeService implements IEmployeeService {

    // 目标类\被代理类\实际类 会在静态代理中创建,不会在外暴露.
    private IEmployeeService target;
    private TxManager tx = new TxManager();
    
    @Override
    public void save() {
        try {
            tx.open();
            
            if (null == target) {
                target = new EmployeeServiceImpl();
                ((EmployeeServiceImpl)target).setEmployeeDAO(new EmployeeDAOImpl());
            }
            target.save();
            
            tx.commit();
        } catch (Exception e) {
            // TODO: handle exception
            tx.rollBack();
        }   
    }

    @Override
    public void update() {
        try {
            tx.open();
            
            if (null == target) {
                target = new EmployeeServiceImpl();
            }
            target.update();
            
            tx.commit();
        } catch (Exception e) {
            // TODO: handle exception
            tx.rollBack();
        }   
    }
}

测试用例

    @Test
    public void testStaticProxy() {
        
        IEmployeeService employeeService = new StaticProxyEmployeeService();
        employeeService.save();
        System.out.println();
        employeeService.update();
        
    }

输出

静态代理和装饰者模式的区别

相同点

不同点

静态代理存在的缺陷

3.动态代理

动态代理在Java中有两种实现如下,这里我们主要介绍基于JDK的动态代理

基于JDK的动态代理是在JVM的运行时的时候通过反射技术动态创建出来,不存在其对应的字节码文件。那么我们要如何来告诉JDK我们要创建的动态代理怎么增加我们想要的功能呢?

必须明确的前提

首先, 我们可以明确的是我们创建出来的代理对象必须是实现真实类所实现的接口,因为我们是对真实类增强

如何让动态代理来做增强
3.1 怎么创建动态代理对象出来

基于上述的讨论我们提出了这个问题, JDK中确实提供了java.lang.reflect.Proxy用于给我们创建动态代理。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
3.2 怎么对真实对象做增强操作

JDK中还提供了java.lang.reflect.InvocationHandler接口,实现该接口类就是具备增强功能的类。JDK会去调用该对象的invoke方法.

3.3 代码演示
public class EmployeeServiceHandler implements InvocationHandler {

    
    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTx(TxManager tx) {
        this.tx = tx;
    }

    private Object target;
    private TxManager tx;
    
    
    public <T> T getProxyObject() {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), 
                this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object ret = nullValue();
        try {
            tx.open();
            ret = method.invoke(target, args);
            tx.commit();
            
        } catch (Exception e) {

            tx.rollBack();
        }
        return ret;
    }

}

Bean的xml配置

<?xml version="1.0" encoding="UTF-8"?>
<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="employeeDAO" class="com.sweetcs._10aop.commons.dao.impl.EmployeeDAOImpl"></bean>

    <bean id="employeeServiceProxy" class="com.sweetcs._10aop.jdk_proxy.EmployeeServiceHandler">
        <property name="target">
            <bean class="com.sweetcs._10aop.commons.service.impl.EmployeeServiceImpl">
                <property name="employeeDAO" ref="employeeDAO"></property>
            </bean>
        </property>
        <property name="tx">
            <bean class="com.sweetcs._10aop.commons.transition.TxManager"></bean>
        </property>
    </bean>
</beans>

测试用例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ProxyTest {

    @Autowired
    EmployeeServiceHandler serviceHandler;
    
    @Test
    public void testCreateEmployeeService() {
        
        IEmployeeService service = serviceHandler.getProxyObject();
        service.save();
        
        service.update();
    }
}

输出

1.4 Spring实现AOP的方式

接着我们就来看看利用Spring中实现AOP。

上一篇下一篇

猜你喜欢

热点阅读