String框架中的代理模式

Spring是一个轻量级J2EE框架.特点:支持IOC,AOP,相当于是容器,其他框架可以很好的跟Spring整合.

IOC: 控制反转.是将创建对象的控制权反转给Spring容器

DI: 依赖注入,其实就是属性赋值.

​ 赋值可以赋值基本类型,引用类型,集合类型(了解)

​ <property name=-"属性名" value="值">

​ <property name=-"属性名" ref="另个bean的id">

DI注入的方式:

​ 1)set方法(重要)

​ 2)构造方法(了解)

注解开发:

​ 创建对象的注解: @Service @Controller @Component @Repository

属性注入的注解: @Value @Autowired @Qualixxx("对象名")

注解生效

​ <context:component-scan base-package="com.qf">

代理模式

接下来学AOP,AOP的底层是动态代理技术,先学习代理技术.

代理单词: proxy

静态代理

需求:实现房东租房.

public interface FangDong {
    void zufang();
}
public class FangdongImpl implements FangDong {
    @Override
    public void zufang() {
        System.out.println("房东租房..." );
    }
}
// 代理
public class FangdongProxy {
​
    // 目标对象
    private FangDong fangDong;
​
    public FangdongProxy(FangDong fangDong){
        this.fangDong = fangDong;
    }
​
    void zufang(){
        // 前置增强
        System.out.println("前:发传单,找客源" );
​
        // 房东租房
        fangDong.zufang();
​
        // 后置增强
        System.out.println("后:签合同" );
    }
}
    // 开始租房
    public static void main(String[] args) {
        // 找代理
        FangdongProxy proxy = new FangdongProxy(new FangdongImpl( ));
        // 真正租房
        proxy.zufang();
    }

现在这是静态代理,代理目的就是将目标方法增强.但是现在是静态代理.

什么是静态代理:

​ 静态代理就是: 一个代理只能做一件事.做其他事情,需要再创建新代理.

​ 目前是房东租房,创建了房东的代理,后续汽车厂要卖车,创建一个汽车的代理商,房东要卖房,找一个卖房的中介;对于代码而言,随着项目的扩展,功能变多,那么需要被代理的类多,那么对应于的代理类就会多.即需要不断根据需要创建代理.

​ 上述问题,如果有一种功能,可以动态的根据情况创建代理.这就是动态代理技术.

动态代理

动态代理会动态的为目标类产生代理对象.动态代理有两种实现方案: 1) JDK动态代理 2) CGLIB动态代理.

1) JDK代理,但是只能代理接口.即目标类要有接口
1) CGLIB代理,可以代理接口,也可以代理类,目录类可以有接口,也可以没有接口

JDK的动态代理

JDK代理,是Java自有技术,无需导入依赖包.

需求: 使用动态代理技术,动态的产生房东代理对象,汽车厂商的代理

public class JDKProxy implements InvocationHandler {
​
    // 目标类
    private Object target;
​
    // 指定目标对象
    public JDKProxy(Object target){
        this.target  = target;
    }
​
​
    /**
     * @param proxy 被代理对象
     * @param method 目标方法
     * @param args   目标方法的参数
     * @return       目标方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 目标方法前:
        System.out.println("前置增强" );
​
        // 目标方法执行
        Object obj = method.invoke(target,args);
​
​
        System.out.println("后置增强" );
        return obj;
    }
}
    public static void main(String[] args) {
        // 目标类的类加载器
        ClassLoader classLoader = FangdongImpl.class.getClassLoader( );
        // 目标类的实现的所有接口
        Class<?>[] interfaces = FangdongImpl.class.getInterfaces( );
        // 目标方法执行处理器
        JDKProxy handler = new JDKProxy(new FangdongImpl( ));
​
        /**
         * 参数1: 目标类的类加载器
         * 参数2: 目标类的所实现的所有接口
         * 参数3:  自己实现的目标方法处理器
         */
        FangDong fangDong = (FangDong) Proxy.newProxyInstance(classLoader, interfaces, handler);
​
        fangDong.zufang();
    }

以上代码了解即可,不用记.

总结:

通过演示发现,有了动态代理技术,不管项目中有多少类,都可以为其产生代理对象.

==结论:(掌握)==

==动态代理,会动态的产生目标类的代理对象,==

==JDK的动态代理,目标类必须有接口.==


在实际开发中,AOP利用动态代理完成哪些事情?

- 日志记录
- 事务开启与提交
- 性能检测
- 权限校验
- 等等

CGLIB动态代理

CGLIB动态代理技术,又叫字节码增强.动态产生代理对象,可以代理接口也可以代理实现类.CGLIB是第三方技术,spring框架中已经整合了cglib的技术,所以只需导入spring-aop的依赖即可.

需求: 使用动态代理技术,动态的产生房东代理对象,汽车厂商的代理

// 代码不需要记
public class MyCglibInterceptor implements MethodInterceptor {
​
    // cglib的增强器
    private Enhancer enhancer = new Enhancer();
​
    // 创建拦截器时,指定目标类的字节码
    public MyCglibInterceptor(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
    }
​
    // 提供一个获得代理对象的方法
    public Object createProxyBean(){
        return enhancer.create();
    }
​
​
    /**
     * @param target 目标对象
     * @param method
     * @param args   目标方法的参数
     * @param methodProxy 目标方法的代理对象
     * @return 目标方法的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 目标方法前:
        System.out.println("前置增强:权限校验,或者事务开启" );
​
​
        // 目标方法执行
        Object ret = methodProxy.invokeSuper(target,args);
​
        // 目标方法前:
        System.out.println("后置增强:日志记录,或者事务提交" );
        return ret;
    }
}
    public static void main(String[] args) {
​
        // 此处,只需要将其他类字节码文件传入此处
        // 即可得到其他类的代理对象
        MyCglibInterceptor interceptor = new MyCglibInterceptor(CarFactoryImpl.class);
​
        CarFactoryImpl carFactory = (CarFactoryImpl) interceptor.createProxyBean( );
​
        carFactory.saleCar();
​
    }

总结:

​ CGLIB动态代理,会动态的给类产生代理对象.

目标类可以没有接口,也可以有接口,都可以代理.

总结:[重点]

代理的目的: 将目标方法功能增强.

代理的方式: 静态代理和动态代理.

什么区别:

​ 静态代理: 每个类都需要自己创建代理对象,一个代理只能代理一个类

​ 动态代理: 给每个类动态产生代理对象.(按需产生)

动态代理如何实现:

​ jdk的动态代理: jdk动态代理只能代理接口(目标类必须有接口)

​ cglib的动态代理:可以代理接口,也可以代理类(目标类可以有接口,也可以没接口)

思考: Mybatis获得Mapper就是动态代理技术.

AOP

OOP面向对象编程

AOP面向切面(Aspect)编程

面向切面编程的作用:就是将项目中与核心逻辑无关的代码横向抽取成切面类,通过织入作用到目标方法,以使目标方法执行前后达到增强的效果.

原理: AOP底层使用的就是动态代理,给AOP指定哪些类型需要增强,就会产生对应的代理对象,代理对象执行方法前后会先执行增强的方法.

好处:

​ 抽取代码,复用,提供效率

减少耦合

利于代码扩展

AOP的术语:

目标类(Target): 被代理的类

连接点(JoinPoint): 目标类中准备被切入的方法

切入点(Pointcut): 真正执行的目标方法

切面(Aspect) : 切面中定义中增强的方法

增强(Advice): 也叫通知,就是在目标方法执行前/后的方法

织入(Weaving): 将增强作用到切入点的过程

AOP编程[重点]

  • 依赖spring-aop.jar

  • 创建所需UserService和UserServiceImpl

  • 创建切面类

  • 配置文件配置切面

  • 测试

入门演示:环绕通知

依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>

接口和实现类

public interface UserService {
    void addUser();
    void deleteUserById();
}
​
public class UserServiceImpl implements UserService{
    @Override
    public void addUser() {
        System.out.println("UserServiceImpl.addUser 执行" );
    }
​
    @Override
    public void deleteUserById() {
        System.out.println("UserServiceImpl.deleteUserById 执行" );
    }
}
​

切面类

import org.aspectj.lang.ProceedingJoinPoint;
/**
 * @desc 切面类
 * 切面类中定义各种增强的方法
 */
public class MyAspect {
​
​
    /**
     * 增强的方法: 环绕通知
     * 参数ProceedingJoinPoint: 目标方法
     * @return
     */
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
​
        // 目标方法前
        System.out.println("前置增强: 开启事务" );
​
        // 目标方法执行
        Object ret = joinPoint.proceed( );
​
        // 目标方法后
        System.out.println("后置增强: 提交事务" );
​
        return ret;
    }
}

spring配置文件

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
<beans>
<!-- 目标类 -->
    <bean id="userService" class="com.wss.service.UserServiceImpl"/>
​
    <!-- 目标类 -->
    <bean id="houseService" class="com.wss.service.HouseServiceImpl"/>
​
    <!-- 创建切面类对象 -->
    <bean id="myAspect" class="com.wss.aspect.MyAspect"/>
​
    <!-- 织入 -->
    <aop:config>
        <!-- 配置切面,引用自定义切面对象 -->
        <aop:aspect ref="myAspect">
            <!-- 下面要做的是将通知作用到目标方法上 -->
​
            <!-- 配置环绕通知 -->
            <!--
                method: 切面类中的方法名
                pointcut: 切入点,内写切入点表达式
                execution(* com.qf.service.*.*(..))
                第一个*, 返回值的意思,*的意思是返回值任意
                com.qf.service.* , 通过路径确定切入点所在类,*的意思是service包下所有类
                .*      ,是方法名,*是指所有方法
                ()       方法的参数列表
                ..       方法内的任意参数
            -->
            <aop:around method="around" pointcut="execution(* com.qf.service.*.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

测试

public class TestAOP {
​
    public static void main(String[] args) {
​
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
​
        // UserService userService = context.getBean("userService", UserService.class);
        HouseService houseService = context.getBean("houseService", HouseService.class);
​
        // 目标方法
        // userService.addUser();
        // userService.deleteUserById();
        
        houseService.addHouse();
    }
}

AOP的动态代理

AOP的动态代理,底层使用了两种代理方案:

默认情况下使用JDK动态代理.即目标类必须有接口,JDK代理接口.当目标类没有实现接口时自动切换使用CGLIB实现动态代理.

其他通知方式

还有其他通知(增强)方式

  • 前置增强(通知)

    • 场景:一般用来做权限校验

  • 后置增强(通知)

    • 场景: 释放资源,或者记录日志

  • 环绕增强(通知)

    • 场景:数据库事务

  • 后置返回增强(通知)

    • 场景:得到目标方法返回值再处理

  • 异常增强(通知)

    • 场景:可以获得目标方法的异常信息,用于记录日志,或者进行异常拦截

切面类

public class MyAspect {
​
​
    /**
     * 前置增强
     * 参数: JoinPoint 目标类对象
     */
    public void before(JoinPoint joinPoint){
        Object target = joinPoint.getTarget( );
        System.out.println("前置增强,获得目标类对象"+target );
​
        Signature signature = joinPoint.getSignature( );
        System.out.println("前置增强,获得目标方法"+signature);
​
        String name = signature.getName( );
        System.out.println("前置增强,获得目标方法名"+name);
​
        System.out.println("前置增强: 权限校验" );
    }
​
​
    /**
     * 后置通知
     */
    public void after(){
        System.out.println("后置通知:记录执行的日志" );
        // 应用场景: 还可以做一些关流,释放资源的动作
    }
​
​
    /**
     * 增强的方法: 环绕通知
     * 参数ProceedingJoinPoint: 目标方法
     * @return
     */
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
​
        // 目标方法前
        System.out.println("环绕-前置增强: 开启事务" );
​
        // 目标方法执行
        Object ret = joinPoint.proceed( );
        System.out.println("环绕-获得目标方法返回值: "+ ret );
        // 目标方法后
        System.out.println("环绕-后置增强: 提交事务" );
​
        return ret;
​
    }
​
    /**
     * 后置返回通知
     * 后置返回能得到目标方法的返回值
     */
    public Object afterReturn(Object ret){
        System.out.println("后置返回通知: "+ ret );
        return ret;
    }
​
    /**
     * 异常通知
     */
    public void myThrow(Exception e){
        // 可以接收到目标方法执行的异常信息
        System.out.println("异常通知,获得异常信息"+e.getMessage()  );
        // 可以做全局异常处理,记录异常信息到日志
    }
​
​
}
​
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
​
​
    <!-- 目标类 -->
    <bean id="userService" class="com.wss.service.UserServiceImpl"/>
​
    <!-- 目标类 -->
    <bean id="houseService" class="com.wss.service.HouseServiceImpl"/>
​
​
    <!-- 创建切面类对象 -->
    <bean id="myAspect" class="com.wss.aspect.MyAspect"/>
​
    <!-- 织入 -->
    <aop:config>
        <!-- 配置切面,引用自定义切面对象 -->
        <aop:aspect ref="myAspect">
            <!-- 下面要做的是将通知作用到目标方法上 -->
​
            <!-- 配置环绕通知 -->
            <!--
                method: 切面类中的方法名
                pointcut: 切入点,内写切入点表达式
                execution(* com.qf.service.*.*(..))
                第一个*, 返回值的意思,*的意思是返回值任意
                com.qf.service.* , 通过路径确定切入点所在类,*的意思是service包下所有类
                .*      ,是方法名,*是指所有方法
                ()       方法的参数列表
                ..       方法内的任意参数
            -->
            <aop:around method="around" pointcut-ref="pointcut"/>
​
            <!-- 将切入点表达式抽取,以便复用 -->
            <aop:pointcut id="pointcut" expression="execution(* com.wss.service.*.*(..))"/>
​
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="pointcut"/>
​
            <!-- 后置通知 -->
            <aop:after method="after" pointcut-ref="pointcut"/>
​
            <!--后置返回
                returning指定增强方法的参数名ret
            -->
            <aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="ret"/>
​
            <!-- 异常通知
                throwing指定增强方法的参数名e
            -->
            <aop:after-throwing method="myThrow" pointcut-ref="pointcut" throwing="e"/>
        </aop:aspect>
    </aop:config>
</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值