Spring增强代理模式

时间:2021-07-08 21:46:08
  • 1. 依赖注入;(掌握)
  • 2. XML自动注入;(掌握)
  • 3. 全注解配置;(掌握)
  • 4. 代理模式;(掌握,难点)

依赖注入

构造参数注入

constructor-arg:构造器注入: index:顺序   name:行参的名称   type:类型    ref:关联另一个bean     id关联;

内部定义一个bean      value:值(普通属性的值-String,Integer,Long,...)

方案一:根据构造器参数的顺序(索引)

<!-- 按照索引注入,索引开始为0 -->

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg index="0" value="666" />

<constructor-arg index="1" value="张二娃" />

</bean>

方案二:根据构造器参数的名称

<!-- 按照名称注入,名称必须一致 -->

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg name="id" value="1" />

<constructor-arg name="name" value="张三娃" />

</bean>

方案三:根据构造器的类型注入

<!-- 按照类型注入,必须一一对应,不能有重复的类型-->

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg type="java.lang.Long" value="1" />

<constructor-arg type="java.lang.String" value="张四娃" />

</bean>

如果有一个参数是我们自己的一个对象,怎么解决?

方案一:先在外面定义好

<bean id="otherBean" class="cn.itsource._01_.OtherBean"/>

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg  value="1" />

<constructor-arg  value="张五娃" />

<constructor-arg ref="otherBean"/>

</bean>

方案二: 使用一个内部的Bean完成(不需要加id)

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg  value="1" />

<constructor-arg  value="张六娃" />

<constructor-arg>

<bean class="cn.itsource._01_.OtherBean"/>

</constructor-arg>

</bean>

.其它简单、集合属性注入

// 简单属性

private Long id;

private String name;

private Boolean sex;

private BigDecimal salary;

// 对象属性

private List<String> list;

private List<OtherBean> otherBeanList;

private Set<String> set;

private Set<OtherBean> otherBeanSet;

private Map<String,Object> map;

//下面这个是重点

private Properties props1;

private Properties props2;

private String[] arrays;

数组(两种方案-掌握): 简写

<property name="arrays" value="A,B,C" />

完整写法

<property name="arrays">

<array>

<value>xxx</value>

<value>yyy</value>

<value>zzz</value>

</array>

</property>

List<String>(了解)

<property name="list">

<list>

<value>xxx</value>

<value>aaa</value>

<value>bbbb</value>

</list>

</property>

Set<String>(了解)

<property name="set">

<set>

<value>xxx</value>

<value>aaa</value>

<value>bbbb</value>

</set>

</property>

1.1. List<OtherBean>(了解)

<property name="otherBeanList">

<list>

<bean class="cn.itsource._01_.OtherBean" />

<bean class="cn.itsource._01_.OtherBean" />

<ref bean="otherBean" />

<ref bean="otherBean" />

</list>

</property>

1.1. Set<OtherBean>(了解)

<property name="otherBeanSet">

<set>

<bean class="cn.itsource._01_.OtherBean" />

<bean class="cn.itsource._01_.OtherBean" />

<ref bean="otherBean" />

<ref bean="otherBean" />

</set>

</property>

1.1. Map(了解)

<property name="map">

<map>

<entry key="key1" value-ref="otherBean"></entry>

<entry key="key2" value="1234"></entry>

</map>

</property>

怎么配置一个Properties对象:

方案1  不支持中文

<property name="props1">

<value>

<!-- <value>

url jdbc:mysql:///xxx

username  xxxx

</value> -->

Jpa.dialect=org.Jpa.dialect.HSQLDialect

Jpa.driverClassName=com.mysql.jdbc.Driver

</value>

</property>

方案二:支持中文

<property name="props2">

<props>

<prop key="Jpa.dialect">org.Jpa.dialect.HSQLDialect</prop>

<prop key="Jpa.driverClassName">com.mysql.jdbc.Driver中文 </prop>

</props>

</property>

#xx.xml

{

  <bean id="person" class="x.xx.xx.Person">

    #此处必须与属性相同

    <property name="properties">

      #使用props进行属性描述

      <props>

        <prop key="name">zhangsan</prop>

        <prop key="age">16</prop>

        <prop key="sex">male</prop>

      <props>

    </property >

  </bean>

  ...

}

XML自动注入

使用XML自动注入,可以简化我们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" default-autowire="byType">

<!--

beans标签(全局):

default-autowire:自动注入

byType : 字段类型必需和bean中的某一个类型相同

如果用了byType,不允许出来两个类型相同的bean

byName :  属性必需和bean的某一个id相同

bean标签(局部)

autowire(单独设置),有单独设置使用单独的,没有单独设置使用全局的

-->

<bean id="userDao" class="cn.itsource._04_autowire.UserDao"></bean>

<bean id="userServices" class="cn.itsource._04_autowire.UserService">

</bean>

<bean id="userServicexxx" class="cn.itsource._04_autowire.UserService">

</bean>

<bean id="userAction" class="cn.itsource._04_autowire.UserAction" autowire="byName">

</bean>

</beans>

1.1.1. byName : 按照属性的名:bean里提供定义一个属性,提供 setXxx方法

default-autowire="byName"

配置文件的bean的id必须和代码bean里的属性一致。

1.1.2. byType   按照注入对象的类型,要求:类型只能配置一个实例

default-autowire="byType"

setXxx方法   注入类的类型(Class)

和配置文件里面的类型进行比较

配置文件里面的类型只能是1个

根节点beans   default-autowire="byName" 对当前配置文件的所有bean都生效

子节点bean autowire="byType"只对当前bean生效

全注解配置

配置context命名空间

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"

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

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd">

第一步:配置让Spring扫描类与支持注解

<!--

@Component  普通仓库

@Repository  仓库(其实就是dao层)

@Service service层

@Controller  控制层(servlet、action)

-->

<!-- 进行包的扫描,去看类上面是否有相应的标签配置 -->

<context:component-scan base-package="cn.itsource._03_anno" />

<!-- 这个不是必须的(spring3.2版本前使用) 配上后兼容性好 -->

<context:annotation-config />

1.1. 调用名称两套方案:

1.1.1. 方案一:使用@Autowired

@Service

public class UserService {

@Autowired

@Qualifier("userJdbcDao")

private IUserDao userDao;

public void save(){

userDao.save();

}

}

方案二:使用@Resource

@Service

public class UserService {

@Resource(name="userJpaDao")

private IUserDao userDao;

public void save(){

userDao.save();

}

}

AOP概述

AOP的使用只存在于一些特定的场合(具有横切逻辑的应用场合),横切逻辑这个解释可能比较抽象,咱们说得再具体一点,AOP可以用于事务管理,日志管理,性能监测等地方。

连接点(Joinpoint):程序执行的某一个特定位置,如类初始前后,方法的运行前后。而Spring只支持方法的连接点

切点(Pointcut):切点可以定位到相应的连接点,一个切点可以定位多个连接点。

增强(Advice):又被称为通知,完成逻辑的增强。

目标对象(Target):增强逻辑织入的目标类。

引介(Introduction):特殊的增强,为类添加一些属性和方法。

织入(Weaving): 将增强添加到目标类的具体连接点上的过程。Spring使用动态代理织入

代理(Proxy):一个类(原类)被织入增强(逻辑)后,就产生一个结果类,称为代理类。

切面(Aspect):由切点和增强组成

AOP联盟:众多开源AOP项目的联合组织,该组织定义了一套规范描述AOP标准。现在大部分的AOP实现都是使用AOP实现的标准。

1.1. Spring实现AOP的方案

Spring实现Aop有两种方案:JDKCGLIB

若目标对象实现了若干接口

spring使用JDK的java.lang.reflect.Proxy类代理。

. 若目标对象没有实现任何接口,

spring使用CGLIB库生成目标对象的子类。

1.对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。

对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,

这种方案应该是备用方案。

2.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要

被通知的方法都被复写,将通知织入。final方法是不允许重写的。

1.1.1. 添加aop命名空间

xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd

<aop:config>

<aop:pointcut expression="execution(* cn.itsource.aop.I*Service.*(..))" id="pointcut" />

<aop:aspect  ref="txManager">

<!-- 前置通知 -->

<aop:before method="begin" pointcut-ref="pointcut"/>

<!-- 后置通知 -->

<aop:after-returning method="commit" pointcut-ref="pointcut"/>

<!-- 异常通知 -->

<aop:after-throwing method="rollback" pointcut-ref="pointcut"/>

<!-- 最终通知 -->

<aop:after method="close" pointcut-ref="pointcut" />

</aop:aspect>

</aop:config>

环绕增强(前面的4个增强就不用了)

<!--

环绕通知:有了环绕,就不需要用另外几个通知(会重复)

如果有两个以上的通知,建议使用环绕

-->

<aop:around method="around" pointcut-ref="pointcut" />

环绕方法

import org.aspectj.lang.ProceedingJoinPoint;

public Object around(ProceedingJoinPoint joinPoint){

//System.out.println(joinPoint.getTarget()); //调用的类

//System.out.println(Arrays.asList(joinPoint.getArgs()));//参递的参数

//System.out.println(joinPoint.getSignature()); //方法签名

Object object = null;

try {

begin();

object = joinPoint.proceed(); //执行相应的代码

commit();

} catch (Throwable e) {

rollback(e);

}finally{

close();

}

return object;

}

1.1. 注解版

1.1.1. 配置文件

<!-- 组件搜索 -->

<context:component-scan base-package="cn.itsource.aopanno" />

<!-- 支持aop注解 -->

<aop:aspectj-autoproxy />

1.1.1. 事务管理器

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.AfterReturning;

import org.aspectj.lang.annotation.AfterThrowing;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.stereotype.Component;

@Component

@Aspect //AOP的类注解

public class TxManager {

//设置切点

@Pointcut("execution(* cn.itsource.aopanno.I*Service.*(..))")

public void pointcut(){}

//前置通知

@Before("pointcut()")

public void begin(){

System.out.println("开启事务....");

}

//后置通知

@AfterReturning("pointcut()")

public void commit(){

System.out.println("提交事务...");

}

//异常通知

@AfterThrowing(pointcut="pointcut()",throwing="e")

public void rollback(Throwable e){

System.out.println("回滚事务....");

System.out.println(e.getMessage());

}

//最终通知

@After("pointcut()")

public void close(){

System.out.println("关闭资源....");

}

}

//温馨提醒:如果要使用环绕通知的话,把其它几个通知的注解去掉(不然会出现重复)

@Around("pointcut()")

public Object around(ProceedingJoinPoint joinPoint){

Object object = null;

try {

begin();

object = joinPoint.proceed(); //执行相应的代码

commit();

} catch (Throwable e) {

rollback(e);

}finally{

close();

}

return object;

}

AOP是面向切面编程,是对咱们OOP的一个补充,Spring的Aop允许咱们在方法的前后加上相应的功能增强。

Spring的Aop是使用代理模式完成的,如果是有接口的类,使用JDK代理模式,如果没有接口的类,使用CGLIB代理模式。

咱们自己使用AOP太麻烦,Spring让我们通过简单的配置即可完成AOP。

第一种配置:XML    第二种配置:注解

配置的时候注意找到何时(方法前后,异常),何地(哪些类的哪些方法),做什么(安全中是添加事务)

1.1. 代理模式定义

代理模式的英文叫做Proxy或Surrogate

① 抽象主题角色:

声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以是使用代理主题。

② 代理主题(Proxy)角色:

代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;

代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主题控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);

代理角色通常在将客户端调用传递给真实的主题之前或之后(前置增强/通知,后置增强/通知),都要执行某个操作,而不是单纯地将调用传递给真实主题对象。

③ 真实主题角色:

定义了代理角色所代表地真实对象

 保存的对象User:
public class User {
private Long id;
private String name;
// getter,setter略
}
模拟一个假的事务管理器TxManager:
public class TxManager {
public void begin(){
System.out.println("开启事务....");
}
public void commit(){
System.out.println("提交事务...");
}
public void rollback(){
System.out.println("回滚事务....");
}
public void close(){
System.out.println("关闭资源....");
}
} 7.3..抽象主题角色
public interface IEmployeeService {
void save(User user);
} 7.3..真实主题角色
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void save(User user) {
System.out.println("保存用户...");
}
} 7.3..代理主题角色
public class EmployeeServiceProxyImpl implements IEmployeeService {
//声明一个变量(代表这个类所代理的真实主题角色)
/*
* 留一个思考题:这里为什么使用接口声明,而不直接使用类?
*/
private IEmployeeService employeeService;
//在构造器中传入真实主题角色(必需要有真实主题角色,代理类才有意义)
public EmployeeServiceProxyImpl(IEmployeeService employeeService) {
this.employeeService = employeeService;
}
/**
* 在真实主题角色执行代码的前后增加额外的事务代码
*/
@Override
public void save(User user) {
TxManager txManager = new TxManager();
try {
txManager.begin();
employeeService.save(user);
txManager.commit();
} catch (Exception e) {
txManager.rollback();
e.printStackTrace();
}finally{
txManager.close();
}
}
} 7.3..调用方
@Test
public void testProxy() throws Exception {
User user = new User();
user.setName("张三");
//创建真实主题对象
IEmployeeService employeeService = new EmployeeServiceImpl();
//创建代理主题对象
employeeService = new EmployeeServiceProxyImpl(employeeService);
//调用方法
employeeService.save(user);
}

1.1.1. JDK动态代理

注:JDK动态代理只能代理有接口的类

①.准备条件

保留静态代理的结构(将代理类删除掉)

下面是完成JDK代理的主要类:

java.lang.reflect.Proxy (可以到jdk文档中找到这个类)

java.lang.reflect.InvocationHandler:代理调用处理程序的接口

 创建JDKProxy类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* InvocationHandler:是代理实例的调用处理程序 实现的接口。
*/
public class JdkProxy implements InvocationHandler{
//定义真实主题角色:目标对象
private Object targetObject;
//传入事务管理器
private TxManager txManager;
public JdkProxy(Object targetObject,TxManager txManager) {
this.targetObject = targetObject;
this.txManager = txManager;
} /**
* proxy:经过jdk的代理对象(基本上没有作用)
* method:实际执行的方法
* args:方法中的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null; //返回的结果
try {
txManager.begin();
result = method.invoke(targetObject, args); //执行直接对象的方法
txManager.commit();
} catch (Exception e) {
txManager.rollback();
e.printStackTrace();
}finally{
txManager.close();
}
return result;
} /**
* 创建一个代理对象
* @return
*/
public Object createProxy(){
return Proxy.newProxyInstance(
this.getClass().getClassLoader(), //类加载器,只要拿到一个即可
targetObject.getClass().getInterfaces() , //实现类的接口集合(因为一个类可以实现多个接口)
this //代理实例的调用处理程序(InvocationHandler的实现类)
);
}
} 测试功能:
@Test
public void testProxy() throws Exception {
User user = new User();
//真实主题角色对象
IEmployeeService employeeService = new EmployeeServiceImpl();
//事务管理器
TxManager txManager = new TxManager();
//创建咱们自定义的一个处理代理功能类(这个类中我们加了一个方法可以直接创建代理对象)
JdkProxy jdkProxy = new JdkProxy(employeeService,txManager);
//获取代理对象
IEmployeeService proxy = (IEmployeeService)jdkProxy.createProxy();
proxy.save(user);
}

1.1.1. CGLIB动态代理

Cglib类似于javassist-3.18.1-GA.jar功能字节码增强,

原来Hibernate3.2之前就是使用cglib来进行字节码增强

①.下面是完成CGLIB的类:

org.springframework.cglib.proxy.Enhancer; 增强器

org.springframework.cglib.proxy.MethodInterceptor; 方法切面(代理实例处理方法功能的接口)

org.springframework.cglib.proxy.MethodProxy;

 CGLIBProxy类代码:
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor{ //定义参数,接收真实的目标对象
private Object targetObject;
//事务对象
private TxManager txManager; public CglibProxy(Object targetObject,TxManager txManager) {
this.targetObject = targetObject;
this.txManager = txManager;
} /**
* proxyObject:CGLIB代理后的对象,一般不用
* method:真实对象的方法
* args:方法的参数
* methodProxy:CGLIB代理后的方法,一般不用
*/
@Override
public Object intercept(Object proxyObject, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
Object result = null; //返回的结果
try {
txManager.begin();
result = method.invoke(targetObject, args); //执行直接对象的方法
txManager.commit();
} catch (Exception e) {
txManager.rollback();
e.printStackTrace();
}finally{
txManager.close();
}
return result;
} /**
* 创建一个代理对象
* @return
*/
public Object createProxy(){
//创建增强器
Enhancer enhancer = new Enhancer();
//创建的代理就是咱们真实目标对象的子类
enhancer.setSuperclass(targetObject.getClass());
//MethodInterceptor就是一个Callback回调
enhancer.setCallback(this);
//创建一个代理对象并返回
return enhancer.create();
}
} 测试代码
@Test
public void testProxy() throws Exception {
User user = new User();
//真实主题角色对象
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
//事务管理器
TxManager txManager = new TxManager();
//创建Cglib代理对象
CglibProxy cglibProxy = new CglibProxy(employeeService, txManager);
//拿到代理对象
EmployeeServiceImpl proxy = (EmployeeServiceImpl)cglibProxy.createProxy();
proxy.save(user);
}
  1. 常见异常

只需要一个类型的Bean,但是出现了2个

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [cn.itsource._03_autowire.UserDao] is defined: expected single matching bean but found 2: userDao,userJdbcDao