spring
Posted m0_56426304
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring相关的知识,希望对你有一定的参考价值。
目录
动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
基于接口的动态代理
涉及的类:Proxy
提供者:JDK官方
基于子类的动态代理
涉及的类:Enhancer
提供者:第三方cglib库
如何创建代理对象:
接口的动态代理:使用Proxy类中的newProxyInstance方法
子类的动态代理:使用Enhancer类中的create方法
创建代理对象的要求:
接口的动态代理:被代理类最少实现一个接口,如果没有则不能使用
子类的动态代理:被代理类不能是最终类
newProxyInstance方法的参数:
ClassLoader:类加载器
他是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法
Class[]:字节码数组
他是用于让代理对象和被代理对象有相同方法。固定写法
InvocationHandler:用于提供增强的代码
他是让我们写如何代理的。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须是匿名内部类;此接口的实现类都是谁用谁写
create方法的参数:
Class:字节码
它是用于指定被代理对象的字节码
Callback:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的;此接口的实现类都是谁用谁写
我们一般写的都是该接口的子接口实现类:MethodInterceptor
基于接口的动态代理代码
Client类
/** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于接口的动态代理: * 涉及的类:Proxy * 提供者:JDK官方 * 如何创建代理对象: * 使用Proxy类中的newProxyInstance方法 * 创建代理对象的要求: * 被代理类最少实现一个接口,如果没有则不能使用 * newProxyInstance方法的参数: * ClassLoader:类加载器 * 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。 * Class[]:字节码数组 * 它是用于让代理对象和被代理对象有相同方法。固定写法。 * InvocationHandler:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 */ IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法参数的含义 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f); } }
IProducer接口
/** * 对生产厂家要求的接口 */ public interface IProducer { /** * 销售 * @param money */ public void saleProduct(float money); /** * 售后 * @param money */ public void afterService(float money); }
Producer类
/** * 一个生产者 */ public class Producer implements IProducer{ /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
基于子类的动态代理代码
Producer类
/** * 一个生产者 */ public class Producer { /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
Cilent类
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于子类的动态代理: * 涉及的类:Enhancer * 提供者:第三方cglib库 * 如何创建代理对象: * 使用Enhancer类中的create方法 * 创建代理对象的要求: * 被代理类不能是最终类 * create方法的参数: * Class:字节码 * 它是用于指定被代理对象的字节码。 * * Callback:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 * 我们一般写的都是该接口的子接口实现类:MethodInterceptor */ Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 执行北地阿里对象的任何方法都会经过该方法 * @param proxy * @param method * @param args * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的 * @param methodProxy :当前执行方法的代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f); } }
接口的动态代理:
new InvocationHander()
作用:执行被代理对象的任何接口方法都会经过该方法
参数:
proxy 代理对象的引用
method 当前执行的方法
args 当前执行方法所需的参数
返回值:和被代理对象方法有相同的返回值
ps.匿名内部类访问外部成员变量时,外部成员变量需要用final修饰
子类的动态代理
三个参数和基于接口的动态代理中invoke方法的参数是一样的
methodProxy :当前执行方法的代理对象
AOP 概述
基于XML的AOP配置
spring中基于XML的AOP配置步骤 1、把通知Bean也交给spring来管理 2、使用aop:config标签表明开始AOP的配置 3、使用aop:aspect标签表明配置切面 id属性:是给切面提供一个唯一标识 ref属性:是指定通知类bean的Id。 4、在aop:aspect标签的内部使用对应标签来配置通知的类型 我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知 aop:before:表示配置前置通知 method属性:用于指定Logger类中哪个方法是前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
环境搭建
第一步:准备必要的代码
实体类,业务层和持久层代码
第二步:拷贝必备的 jar 包到工程的 lib 目录此处要拷贝 spring 的 ioc 和 aop 两组 jar 包第三步:创建 spring 的配置文件并导入约束此处要导入 aop 的约束<?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>
第四步:配置 spring 的 ioc<!-- 配置 service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置 dao --> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <property name="dbAssit" ref="dbAssit"></property> </bean> <!-- 配置数据库操作对象 --> <bean id="dbAssit" class="com.itheima.dbassit.DBAssit"> <property name="dataSource" ref="dataSource"></property> <!-- 指定 connection 和线程绑定 --> <property name="useCurrentConnection" value="true"></property> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property> </bean>
第五步:抽取公共代码制作成通知/** * 事务控制类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ public class TransactionManager { //定义一个 DBAssit private DBAssit dbAssit ; public void setDbAssit(DBAssit dbAssit) { this.dbAssit = dbAssit; } //开启事务 public void beginTransaction() { try { dbAssit.getCurrentConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } //提交事务 public void commit() { try { dbAssit.getCurrentConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } //回滚事务 public void rollback() { try { dbAssit.getCurrentConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } //释放资源 public void release() { try { dbAssit.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } } }
配置步骤
第一步:把通知类用 bean 标签配置起来<!-- 配置通知 --> <bean id="txManager" class="com.itheima.utils.TransactionManager"> <property name="dbAssit" ref="dbAssit"></property> </bean>
第二步:使用 aop:config 声明 aop 配置aop:config:作用: 用于声明开始 aop 的配置< aop:config ><!-- 配置的代码都写在此处 --></ aop:config >第三步:使用 aop:aspect 配置切面aop:aspect:作用:用于配置切面属性:id :给切面提供一个唯一标识。ref :引用配置好的通知类 bean 的 id 。< aop:aspect id = "txAdvice" ref = "txManager" ><!-- 配置通知的类型要写在此处 --></ aop:aspect >第四步:使用 aop:pointcut 配置切入点表达式aop:pointcut :作用:用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。属性:expression :用于定义切入点表达式。id :用于给切入点表达式提供一个唯一标识<aop:pointcut expression="execution( public void com.itheima.service.impl.AccountServiceImpl.transfer( java.lang.String, java.lang.String, java.lang.Float) )" id="pt1"/>
第五步:使用 aop:xxx 配置对应的通知类型aop:before作用:用于配置前置通知。指定增强的方法在切入点方法之前执行属性:method: 用于指定通知类中的增强方法名称ponitcut-ref :用于指定切入点的表达式的引用poinitcut :用于指定切入点表达式执行时间点:切入点方法执行之前执行< aop:before method = "beginTransaction" pointcut-ref = "pt1" />aop:after-returning作用:用于配置后置通知属性:method: 指定通知中方法的名称。pointct: 定义切入点表达式pointcut-ref: 指定切入点表达式的引用执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行< aop:after-returning method = "commit" pointcut-ref = "pt1" />aop:after-throwing作用:用于配置异常通知属性:method : 指定通知中方法的名称。pointct : 定义切入点表达式pointcut-ref : 指定切入点表达式的引用执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个< aop:after-throwing method = "rollback" pointcut-ref = "pt1" />aop:after作用:用于配置最终通知属性:method: 指定通知中方法的名称。pointct: 定义切入点表达式pointcut-ref: 指定切入点表达式的引用执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。< aop:after method = "release" pointcut-ref = "pt1" />
切入点表达式说明
execution: 匹配方法的执行 ( 常用 )execution( 表达式 )表达式语法: execution ( [ 修饰符 ] 返回值类型 包名 . 类名 . 方法名 ( 参数 ))写法说明:全匹配方式:public voidcom.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)访问修饰符可以省略void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)返回值可以使用 * 号,表示任意返回值*com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)包名可以使用 * 号,表示任意包,但是有几级包,需要写几个 ** *.*.*.*.AccountServiceImpl.saveAccount(com.itheima.domain.Account)使用 .. 来表示当前包,及其子包* com..AccountServiceImpl.saveAccount(com.itheima.domain.Account)类名可以使用 * 号,表示任意类* com..*.saveAccount(com.itheima.domain.Account)方法名可以使用 * 号,表示任意方法* com..*.*( com.itheima.domain.Account)参数列表可以使用 * ,表示参数可以是任意数据类型,但是必须有参数* com..*.*(*)参数列表可以使用 .. 表示有无参数均可,有参数可以是任意类型* com..*.*(..)全通配方式:* *..*.*(..)注:通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。execution( * com.itheima.service.impl.*.*(..) )
环绕通知
配置方式 :<aop:config> <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/> <aop:aspect id="txAdvice" ref="txManager"> <!-- 配置环绕通知 --> <aop:around method="transactionAround" pointcut-ref="pt1"/> </aop:aspect> </aop:config>
aop:around作用:用于配置环绕通知属性:method :指定通知中方法的名称。pointct :定义切入点表达式pointcut-ref :指定切入点表达式的引用说明:它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。注意:通常情况下,环绕通知都是独立使用的/** * 环绕通知 * @param pjp * spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。 * 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。 * @return */ public Object transactionAround(ProceedingJoinPoint pjp) { //定义返回值 Object rtValue = null; try { //获取方法执行所需的参数 Object[] args = pjp.getArgs(); //前置通知:开启事务 beginTransaction(); //执行方法 rtValue = pjp.proceed(args); //后置通知:提交事务 commit(); }catch(Throwable e) { //异常通知:回滚事务 rollback(); e.printStackTrace(); }finally { //最终通知:释放资源 release(); } return rtValue; }
基于注解的 AOP 配置
环境搭建
第一步:准备必要的代码和 jar 包实体类,业务层和持久层代码第二步:在配置文件中导入 context 的名称空间<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置数据库操作对象 --> <bean id="dbAssit" class="com.itheima.dbassit.DBAssit"> <property name="dataSource" ref="dataSource"></property> <!-- 指定 connection 和线程绑定 --> <property name="useCurrentConnection" value="true"></property> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property> </bean> </beans>
第三步:把资源使用注解配置/** * 账户的业务层实现类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @Service("accountService") public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; } /** * 账户的持久层实现类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @Repository("accountDao") public class AccountDaoImpl implements IAccountDao { @Autowired private DBAssit dbAssit ; }
第四步:在配置文件中指定 spring 要扫描的包
<!-- 告知 spring,在创建容器时要扫描的包 --> <context:component-scan base-package="com.itheima"></context:component-scan>
配置步骤
第一步:把通知类也使用注解配置/** * 事务控制类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @Component("txManager") public class TransactionManager { //定义一个 DBAssit @Autowired private DBAssit dbAssit ; }
第二步:在通知类上使用@Aspect 注解声明为切面作用:把当前类声明为切面类/** * 事务控制类 * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @Component("txManager") @Aspect//表明当前类是一个切面类 public class TransactionManager { //定义一个 DBAssit @Autowired private DBAssit dbAssit ; }
第三步:在增强的方法上使用注解配置通知@Before作用:把当前方法看成是前置通知。属性:value :用于指定切入点表达式,还可以指定切入点表达式的引用。//开启事务 @Before("execution(* com.itheima.service.impl.*.*(..))") public void beginTransaction() { try { dbAssit.getCurrentConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } }
@AfterReturning作用:把当前方法看成是后置通知。属性:value :用于指定切入点表达式,还可以指定切入点表达式的引用//提交事务 @AfterReturning("execution(* com.itheima.service.impl.*.*(..))") public void commit() { try { dbAssit.getCurrentConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } }
@AfterThrowing作用:把当前方法看成是异常通知。属性:value :用于指定切入点表达式,还可以指定切入点表达式的引用//回滚事务 @AfterThrowing("execution(* com.itheima.service.impl.*.*(..))") public void rollback() { try { dbAssit.getCurrentConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } }
@After作用:把当前方法看成是最终通知。属性:value :用于指定切入点表达式,还可以指定切入点表达式的引用//释放资源 @After("execution(* com.itheima.service.impl.*.*(..))") public void release() { try { dbAssit.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } }
第四步:在 spring 配置文件中开启 spring 对注解 AOP 的支持
<!-- 开启 spring 对注解 AOP 的支持 --> <aop:aspectj-autoproxy/>
环绕通知注解配置
@Around作用:把当前方法看成是环绕通知。属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。
/** * 环绕通知 * @param pjp * @return */ @Around("execution(* com.itheima.service.impl.*.*(..))") public Object transactionAround(ProceedingJoinPoint pjp) { //定义返回值 Object rtValue = null; try { //获取方法执行所需的参数 Object[] args = pjp.getArgs(); //前置通知:开启事务 beginTransaction(); //执行方法 rtValue = pjp.proceed(args); //后置通知:提交事务 commit(); }catch(Throwable e) { //异常通知:回滚事务 rollback(); e.printStackTrace(); }finally { //最终通知:释放资源 release(); } return rtValue; }
切入点表达式注解
@Pointcut作用:指定切入点表达式属性:value :指定表达式的内容@Pointcut( "execution(* com.itheima.service.impl.*.*(..))" )private void pt1() {}引用方式:
/** * 环绕通知 * @param pjp * @return */ @Around("pt1()")//注意:千万别忘了写括号 public Object transactionAround(ProceedingJoinPoint pjp) { //定义返回值 Object rtValue = null; try { //获取方法执行所需的参数 Object[] args = pjp.getArgs(); //前置通知:开启事务 beginTransaction(); //执行方法 rtValue = pjp.proceed(args); //后置通知:提交事务 commit(); }catch(Throwable e) { //异常通知:回滚事务 rollback(); e.printStackTrace(); }finally { //最终通知:释放资源 release(); } return rtValue; }
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
以上是关于spring的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot:thymeleaf 没有正确渲染片段
What's the difference between @Component, @Repository & @Service annotations in Spring?(代码片段
spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段
Spring Rest 文档。片段生成时 UTF-8 中间字节无效 [重复]
解决spring-boot启动中碰到的问题:Cannot determine embedded database driver class for database type NONE(转)(代码片段