1. spring的AOP
讲springaop之前先讲一下Java的两种代理模式,静态代理与动态代理。
什么是静态代理? 代理类是自己编写的。
什么是动态代理? 代理类是jdk帮我们生成的。
1.1 静态代理
静态代理是在程序运行前手动创建代理类,代理类和目标类需要实现相同接口;
需求: 演员只负责表演,创建经纪人来代理演员,处理演员除表演之外的功能。
静态代理的步骤
1.创建演员接口IActor和演员WangBaoQiang
2.创建经纪人SongZhe实现IActor
3.将WangBaoQiang作为成员变量。
4.测试
1.创建演员接口IActor和演员WangBaoQiang类
IActor.java
package com.vv.sh;
public interface IActor {
public void sing();
public void dance();
}
WangBaoQiang.java
package com.vv.sh.dao;
import com.vv.sh.pojo.Account;package com.vv.sh;
public class WangBaoQiang implements IActor {
@Override
public void sing() {
System.out.println("王宝强唱歌");
}
@Override
public void dance() {
System.out.println("王宝强跳舞");
}
}
2.创建经纪人SongZhe实现IActor,将WangBaoQiang作为成员变量。
SongZhe.java
package com.vv.sh;
public class SongZhe implements IActor {
private WangBaoQiang wangBaoQiang;
public SongZhe(WangBaoQiang wangBaoQiang) {
this.wangBaoQiang = wangBaoQiang;
}
@Override
public void sing() {
//增强的功能
System.out.println("唱歌的经费的洽谈");
//调用原有功能
wangBaoQiang.sing();
}
@Override
public void dance() {
//增强的功能
System.out.println("跳舞的经费的洽谈");
//调用原有功能
wangBaoQiang.dance();
}
}
3.测试
public class Test {
public static void main(String[] args) {
WangBaoQiang wangBaoQiang = new WangBaoQiang();
IActor actor = new SongZhe(wangBaoQiang);
//执行代理对象的方法
actor.dance();
actor.sing();
}
}
结果:
小结:静态代理的问题
如果目标类中有多个方法都需要增强,我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改
1.2 动态代理
代理类在程序运行时创建的方式被称为动态代理。
优势:相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
jdk的动态代理是基于接口的实现类,所以必须要有接口
cglib代理是基于子类继承父类的,不需要接口。
1.2.1 jdk的动态代理
目标:使用jdk的动态代理针对WangBaoQiang进行代理
1、JDK动态代理:基于接口的;
2、JDK动态代理实现要点:
Proxy类
newProxyInstance静态方法
InvocationHandler增强方法
package com.vv.sh;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//jdk的动态代理
public class Test {
public static void main(String[] args) {
WangBaoQiang wangBaoQiang = new WangBaoQiang();
ClassLoader classLoader = wangBaoQiang.getClass().getClassLoader();
Class<?>[] interfaces = wangBaoQiang.getClass().getInterfaces();
/*
参数1:类加载器
参数2:接口
参数3: 处理器对象
*/
IActor songZhe = (IActor) Proxy.newProxyInstance(classLoader, interfaces,
new InvocationHandler() {
/**
*
* @param proxy 代理对象本身,songzhe
* @param method 被代理对象执行的方法
* @param args 方法传递的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//判断方法名
if("sing".equals(methodName)){
System.out.println("洽谈唱歌的费用");
}else if("dance".equals(methodName)){
System.out.println("洽谈跳舞的费用");
}
//执行原有功能
method.invoke(wangBaoQiang,args);
return null;
}
});
songZhe.sing();
songZhe.dance();
}
}
结果:
jdk生成的代理类如下
1.2.2 cglib动态代理
如果目标类没有实现接口呢?
那么就无法使用JDK的动态代理,因此这种方式有其局限性,必须实现一个接口。
可以使用的方案:
使用cglib动态代理:基于子类继承父类的,不需要接口。
实现步骤:
1.引入cglib的依赖
2.编写wangbaoqiang,不需要使用IActor接口
3.编写cglib的api创建代理对象
1.引入cglib的依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
2.编写wangbaoqiang类,不需要使用IActor接口
package com.vv.sh;
public class WangBaoQiang {
public void sing() {
System.out.println("王宝强唱歌");
}
public void dance() {
System.out.println("王宝强跳舞");
}
}
3.编写cglib的api创建代理对象
package com.vv.sh;
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 Test {
public static void main(String[] args) {
WangBaoQiang wangBaoQiang = new WangBaoQiang();
//cglib的api
/**
* 参数1: 代理对象需要继承的类,
* 参数2: 处理器,作用类似jdk动态代理中的InvocationHanlder
*/
WangBaoQiang songzhe = (WangBaoQiang) Enhancer.create(WangBaoQiang.class, new MethodInterceptor() {
/**
*
* @param o 代理对象本身
* @param method 被代理的方法
* @param objects 方法的参数
* @param methodProxy 代理方法本身,没有暖用
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//增强的功能
String methodName = method.getName();
if("sing".equals(methodName)){
System.out.println("洽谈唱歌的费用");
}else if("dance".equals(methodName)){
System.out.println("洽谈跳舞的费用");
}
//原有功能
method.invoke(wangBaoQiang,objects);
return null;
}
});
songzhe.dance();
songzhe.sing();
}
}
1.3 springaop的XML方式
1.3.1 aop介绍
aop:意思是面向切面的编程,就是采用动态代理技术,在不修改源码的情况下,对功能进行增强。
上面已经讲解了动态代理,下面看下spring的aop是如何实现的
1.3.2 相关专业术语
aop中有几个比较重要的相关专业术语
切面:用来配置增强与切入点的关系
切入点:需要被增强的方法
增强:也叫通知,增强的功能所在的方法
需求:
操作数据库Account表,查询所有账户信息
使用spring的aop,完成日志的输出
1.3.3 步骤:
环境准备
1.引入依赖,spring-context,junit,spring-test,mysql驱动,spring-jdbc
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
2.编写实体类,service、dao层的接口和实现类
实体类
package com.vv.pojo;
public class Account {
private int id;
private String accountName;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", accountName='" + accountName + '\'' +
", money=" + money +
'}';
}
}
service接口和实现类
package com.itheima.service;
import com.itheima.pojo.Account;
import java.util.List;
public interface AccountService {
public List<Account> findAllAccounts();
}
package com.vv.service.impl;
import com.vv.dao.AccountDao;
import com.vv.pojo.Account;
import com.vv.service.AccountService;
import java.util.List;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAllAccounts() {
return accountDao.findAllAccounts();
}
}
dao接口和实现类
package com.vv.dao;
import com.vv.pojo.Account;
import java.util.List;
public interface AccountDao {
List<Account> findAllAccounts();
}
package com.vv.dao.impl;
import com.vv.dao.AccountDao;
import com.vv.pojo.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public List<Account> findAllAccounts() {
String sql = "select * from account";
List<Account> accountList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Account.class));
return accountList;
}
}
3.编写spring的配置
applicationContetx.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"
xmlns:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 引入外部配置-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- bean definitions here -->
<bean id="accountService" class="com.vv.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.vv.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
</bean>
<!--
aop的配置:
在 1切面中 配置 2切入点 和 3增强 的 4关系
-->
<!-- 创建增强所在的对象-->
<bean id="logService" class="com.vv.service.impl.LogServiceImpl"></bean>
<!--声明开始配置aop-->
<!-- 注意名称空间是aop的名称空间-->
<aop:config>
<!--声明切面
ref: 增强所在的对象
-->
<aop:aspect ref="logService">
<!--
配置增强和切入点的关系
before:前置通知,增强在核心功能之前执行。
method: 增强
pointcut: 切入点
-->
<!--
切入点表达式:
语法:
修饰符 返回值 包名.类名.方法名(参数)
1.修饰符可以省略不写
2.*可以代表任意返回值
* com.vv.service.impl.AccountServiceImpl.findAllAccounts(..)
3.*可以代表任意包名,但是一级包,需要一个*
* *.*.*.*.AccountServiceImpl.*(..)
4. *..代表多级包
* *..AccountServiceImpl.*(..)
5. *可以代表任意类名
* *..*.*(..)
6. *可以代表任意方法名
* com.vv.service.impl.AccountServiceImpl.*(..)
7. *可以代表任意参数,但是一个*标识一个参数
* com.vv.service.impl.AccountServiceImpl.*(*)
8. ..代表任意多个参数
* com.vv.service.impl.AccountServiceImpl.*(..)
在实际企业开发,常用的切入点表达式
应该在确保精准的情况下,保证其简洁。
* com.vv.service.impl.*.*(..)
-->
<!--
aop:before: 前置通知,增强在切入点之前执行
aop:after:最终通知,无论如何都会执行
aop:after-returning :后置通知,没有错误的情况下才会执行
aop:after-throwing: 异常通知,出现异常会执行
aop:around: 环绕通知,自定义通知的位置
-->
<!--
<aop:before method="printLog" pointcut="execution(* com.vv.service.impl.AccountServiceImpl.*(..))"></aop:before>
-->
<!--抽取切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.vv.service.impl.AccountServiceImpl.*(..))"></aop:pointcut>
<!--
<aop:after-returning method="printLog" pointcut-ref="pt1"></aop:after-returning>
-->
<!--
<aop:after-throwing method="printLog" pointcut-ref="pt1"></aop:after-throwing>
-->
<!--
<aop:after method="printLog" pointcut-ref="pt1"></aop:after>
-->
<aop:around method="printLog2" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
jdbc.properties
4.编写测试类进行测试
package com.vv.service;
import com.vv.pojo.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void findAllAccounts() {
List<Account> allAccounts = accountService.findAllAccounts();
for(Account a:allAccounts){
System.out.println(a);
}
}
}
XML方式
步骤:
1.引入spring-aspect依赖
2.编写增强类
3.配置aop
a.<aop:config> 声明开始配置aop
b.<aop:aspect> 声明切面
c.<aop:before point-cut="切入点" method='增强'>
before:前置通知(就是关系)
1.引入spring-aspect依赖
<!--spring的aop相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
2.编写增强类
package com.vv.service;
public interface LogService {
public void printLog();
}
package com.vv.service.impl;
import com.vv.service.LogService;
public class LogServiceImpl implements LogService {
@Override
public void printLog() {
System.out.println("调用方法前,输入日志信息,******");
}
}
3.配置aop
<!-- 创建增强所在的对象-->
<bean id="logService" class="com.vv.service.impl.LogServiceImpl"></bean>
<!--声明开始配置aop-->
<!-- 注意名称空间是aop的名称空间-->
<aop:config>
<!--声明切面
ref: 增强所在的对象
-->
<aop:aspect ref="logService">
<!--
配置增强和切入点的关系
before:前置通知,增强在核心功能之前执行。
method: 增强
pointcut: 切入点
-->
<!--
切入点表达式:
语法:
修饰符 返回值 包名.类名.方法名(参数)
1.修饰符可以省略不写
2.*可以代表任意返回值
* com.vv.service.impl.AccountServiceImpl.findAllAccounts(..)
3.*可以代表任意包名,但是一级包,需要一个*
* *.*.*.*.AccountServiceImpl.*(..)
4. *..代表多级包
* *..AccountServiceImpl.*(..)
5. *可以代表任意类名
* *..*.*(..)
6. *可以代表任意方法名
* com.vv.service.impl.AccountServiceImpl.*(..)
7. *可以代表任意参数,但是一个*标识一个参数
* com.vv.service.impl.AccountServiceImpl.*(*)
8. ..代表任意多个参数
* com.vv.service.impl.AccountServiceImpl.*(..)
在实际企业开发,常用的切入点表达式
* com.vv.service.impl.*.*(..)
-->
<!--
aop:before: 前置通知,增强在切入点之前执行
aop:after:最终通知,无论如何都会执行
aop:after-returning :后置通知,没有错误的情况下才会执行
aop:after-throwing: 异常通知,出现异常会执行
aop:around: 环绕通知,自定义通知的位置
-->
<aop:before method="printLog" pointcut="execution(* com.vv.service.impl.AccountServiceImpl.*(..))">
</aop:before>
</aop:aspect>
</aop:config>
1.4 springAOP的半XML半注解方式
实现步骤:
1.复制上述工程
2.ioc注解(详细步骤省略)
3.使用注解aop来替代xml的aop
a. <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 开启注解aop
b. @Aspect:声明切面类
c. @Before添加到通知上:声明关系,value属性就是切入点表达式
d: @PointCut抽取公共的切入点表达式
@Override
@Before("execution(* com.vv.service.impl.AccountServiceImpl.findAllAccounts(..))")
public void printLog(JoinPoint joinPoint) {
//获取切入点的签名信息,签名信息包含了切入点的名字,方法,类等等。。。
Signature signature = joinPoint.getSignature();
//获取切入点的方法名字
String name = signature.getName();
System.out.println("开始执行方法----"+name);
}
1.5 springAOP的纯注解方式
纯注解就是用配置类代替配置文件所有配置
关于开启aop注解的注解@EnableaApectjAutoproxy
步骤:
1.复制上述工程:
2.创建一个类,使用@Configuration声明配置类
3.@PropertySource引入外部配置
4.@ComponementScan开启注解扫描
5.@Bean创建对象
6.@Import导入其他配置类
7.@EnableAspectjAutoproxy
SpringConfig.java
package com.vv.config;
import org.springframework.context.annotation.*;
@Configuration//声明当前是一个配置类
@PropertySource("classpath:jdbc.properties")// 引入外部配置
@ComponentScan("com.vv") //开启注解扫描
@EnableAspectJAutoProxy //开启注解aop
@Import(JdbcConfig.class)
public class SpringConfig {
}
JdbcConfig.java
package com.vv.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
public class JdbcConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driverClass}")
private String driverClass;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setPassword(password);
dataSource.setUsername(username);
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
}