? ? ? ? 之前我们学习了spring中的IOC,IOC的底层原理就是反射,今天学习spring中的AOP,AOP的底层原理就是动态代理,首先我们要知道java中的代理都有哪些?
? ? ? ? java的代理有静态代理和动态代理,静态代理就是一种设计模式,这里就不过多介绍。什么是动态代理呢,动态代理就是在内存从动态的生成一个接口的实现类或者一个类 的子类 。
????????分别有两种方式进行动态代理,一种是JDK 自带的,不用额外的添加依赖,但只能动态代理接口类型的,另一种是CGLIB提供的动态代理,需要额外的添加cglib的依赖,可以动态代理接口和类。下面就分别演示两种方法的实现。 代理测试方法执行的时间
? ? ? ? JDK的
接口
package com.qfedu.demo.jdk;
public interface JiSuanQi {
int add(int a,int b);
int mines(int a,int b);
}
实现类
package com.qfedu.demo.jdk;
public class JiSuanQiImpl implements JiSuanQi {
@Override
public int add(int a, int b) {
int c = a+b;
System.out.println(c);
return c;
}
@Override
public int mines(int a, int b) {
int c = a-b;
System.out.println(c);
return c;
}
}
测试
package com.qfedu.demo.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK 动态代理,实际上就是动态的给 Calculator 这个接口生成一个实现类.
*
* JDK 动态代理,只能代理接口,对于没有接口的类,是无法使用 JDK 动态代理的
*
* <p>
* public class A implements Calculator{
* CalculatorImpl impl = new CalculatorImpl();
* int add(int a,int b){
* //记录开始时间
* int a = impl.add(a,b);
* //记录结束时间
* //打印执行耗时
* //返回 a
* <p>
* }
* }
*/
public class Demo1 {
public static void main(String[] args) {
ClassLoader loder = Demo1.class.getClassLoader();
Class<?>[] interfaces = {JiSuanQi.class};
InvocationHandler handler = new InvocationHandler() {
/**
*
* @param o 这个是具体的代理对象
* @param method 这个是代理的方法
* @param objects 代理的方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
long l = System.currentTimeMillis();
Object invoke = method.invoke(new JiSuanQiImpl(), objects);
long l1 = System.currentTimeMillis();
System.out.println("该方法耗费毫秒值为"+(l1-l));
return invoke;
}
};
//调用这个方法获取一个动态代理对象
//1. 类加载器
//2. 生成的动态代理对象所实现的接口
//3. 动态代理对象具体的处理逻辑
JiSuanQi o = (JiSuanQi) Proxy.newProxyInstance(loder, interfaces, handler); //这个返回对象就是上面的o
o.add(2,1); //这里是有返回值的 但这个返回值是上面的invoke
}
}
以上就是JDK的动态代理
CGLIB的动态代理
首先引入依赖
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
?然后创建父类
package com.qfedu.demo.cglib;
public class JiSuanQi {
public int add(int a, int b) {
int c = a+b;
System.out.println(c);
return c;
}
public int mines(int a, int b) {
int c = a-b;
System.out.println(c);
return c;
}
}
再创建一个拦截器
package com.qfedu.demo.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 首先定义一个计算器的拦截器
* <p>
* 这是一个方法的拦截器,即将来执行计算器的方法时候,会被自动拦截下来。
*/
public class JiSuanQiInterceptor implements MethodInterceptor {
/**
* 这个方法类似于 JDK 动态代理中的 invoke 方法,要额外加进来的代码,就放在这个里边
*
* @param o 代理对象 不可以直接调用method中的方法 所以cglib增加了一个methodProxy参数, 代理对象就可以通过这个参数调用方法
* @param method 代理的方法 这个不可以放代理对象 要放的是实现类的对象 和参数
* @param objects 方法的参数
* @param methodProxy 方法代理对象 里面的放的就是代理对象 和参数
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long l = System.currentTimeMillis();
//调用父类中的方法,父类就是 CalculatorImpl,子类是一个动态生成的类
//调用父类的方法,就完成了真正的计算器方法的调用
//Object o1 = methodProxy.invokeSuper(o, objects);
//调用接口的方法
Object invoke = method.invoke(new Ji1Impl(), objects);
long l1 = System.currentTimeMillis();
System.out.println("该方法耗费毫秒值为"+(l1-l));
return invoke;
}
}
测试类
package com.qfedu.demo.cglib;
import net.sf.cglib.proxy.Enhancer;
public class Demo {
public static void main(String[] args) {
//创建字节增强器
Enhancer enhancer = new Enhancer();
//设置父类
//enhancer.setSuperclass(JiSuanQi.class);
//设置回调 即设置方法拦截器
enhancer.setCallback(new JiSuanQiInterceptor());
//这里拿到的,并不是 JiSuanQi 这个类的对象本身,而是他的子类的一个对象
JiSuanQi jiSuanQi = (JiSuanQi) enhancer.create();
jiSuanQi.mines(2,1);
//设置接口
Class<?>[] interfaces = {Ji1.class};
enhancer.setInterfaces(interfaces);
Ji1 ji1 = (Ji1) enhancer.create();
ji1.add(2,2);
}
}
以上就是java的两种动态代理,spring的AOP的底层原理就是使用这两种动态代理的,默认接口使用JDK的,类的使用CGLIB的
? ? ? ? 那么在spring容器中要怎么设置动态代理呢,我们可以通过xml配置文件来设置,也可以通过java配置类来设置。
? ? ? ? 先以xml配置文件来配置
先导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qfedu</groupId>
<artifactId>aop_xml</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.9.1</version>
</dependency>
</dependencies>
</project>
和刚才一样创建一个接口和实现类
package com.qfedu.demo.service;
import com.qfedu.demo.MyAction;
@MyAction
public class JiSuanQiImpl implements JiSuanQi {
@Override
public int add(int a, int b) {
System.out.println(a+b);
return a+b;
}
@Override
public void mine(int a, int b) {
// int s =1/0;
System.out.println(a-b);
}
}
package com.qfedu.demo.service;
public interface JiSuanQi {
int add(int a,int b);
void mine(int a,int b);
}
然后创建通知类 通知类里面就是编写你要做的事情的代码
package com.qfedu.demo.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* AOP 面向切面编程
* <p>
* 1. 切点(PointCut):要添加的代码的位置,就是切点
* 2. 增强/通知(Advice):要添加的代码
* 3. 切面(Aspect): 切点+通知
* <p>
* 在 Spring AOP 中,有五种不同类型的 Advice:
* 1. 前置通知:目标方法执行之前会执行
* 2. 后置通知:目标方法执行之后会执行
* 3. 异常通知:目标方法抛出异常的时候会触发
* 4. 返回通知:目标方法返回值的时候会触发
* 5. 环绕通知:前四个之和。
*/
public class LogAdvice {
//前置通知 名字随意
public void before(JoinPoint jp){
//获取被拦截下来的方法名称
String name = jp.getSignature().getName();
System.out.println(name+"开始执行了");
}
//后置通知
public void after(JoinPoint jp){
//获取被拦截下来的方法名称
String name = jp.getSignature().getName();
System.out.println(name+"执行结束了");
}
//异常通知
public void ex(JoinPoint jp,Exception e){
String name = jp.getSignature().getName();
System.out.println(name+"方法抛出了"+e.getMessage()+"异常");
}
//返回值通知
public void afterReturning(JoinPoint jp,Object r){
String name = jp.getSignature().getName();
System.out.println(name+"方法的返回值是"+r);
}
//环绕通知
public Object around(ProceedingJoinPoint point){
String name = point.getSignature().getName();
try {
//前置通知
System.out.println(name+"方法执行了。。。");
//这个方法就相当于是JDK动态代理中的 Object o = method.invoke(o,objects)
Object proceed = point.proceed();
//后置通知
System.out.println(name+"方法结束了....");
//返回值通知
System.out.println(name+"返回值为"+proceed);
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
//异常通知
System.out.println(name+"方法抛出了"+throwable.getMessage()+"异常");
}
return null;
}
}
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: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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1 这里先要配置一个实现类的bean-->
<bean class="com.qfedu.demo.service.JiSuanQiImpl" id="jiSuanQi"/>
<!-- 开始配置AOP-->
<!-- 2 配置通知bean-->
<bean id="logAdvice" class="com.qfedu.demo.aop.LogAdvice"/>
<!-- spring的aop默认情况下是有接口的就调用jdk的动态代理,没有接口调用的是cglib的动态代理 要是想都用cglib的动态代理 只需要在
aop:config标签里将proxy-target-class属性该为true即可
3 -->
<aop:config proxy-target-class="true">
<!--这个地方用来配置切点
第一个 int 表示方法的返回值
接下来是方法的全路径
最后还要指定方法的参数类型(防止方法有重载)
-->
<!-- 4.1 <aop:pointcut id="pc1" expression="execution(int com.qfedu.demo.service.JiSuanQiImpl.add(int,int))"/>-->
<!--
这里是拦截一个包下所有类的所有方法
第一个 * 表示返回值任意
第二个 * 表示 service 包下的所有类
第三个 * 表示方法名任意
.. 表示参数任意:类型任意、个数任意
-->
<!-- 4.2 <aop:pointcut id="pc1" expression="execution(* com.qfedu.demo.service.*.*(..))"/>-->
<!-- 当注解在方法上时-->
<!-- 4.3 <aop:pointcut id="pc1" expression="@annotation(com.qfedu.demo.MyAction)"/>-->
<!-- 4.5 当注解在类上时-->
<aop:pointcut id="pc1" expression="@within(com.qfedu.demo.MyAction)"/>
<!--
这个地方是配置切面的地方,切面=切点+通知
5 -->
<aop:aspect ref="logAdvice">
<!--
aop:before 这个节点用来配置前置通知,method 指的是前置通知的方法名称,pointcut-ref 指的是引用的切点
6 -->
<!-- <aop:before method="before" pointcut-ref="pc1"/> 前置通知
<aop:after method="after" pointcut-ref="pc1"/>后置通知
<aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="r"/> 返回值通知
<aop:after-throwing method="ex" pointcut-ref="pc1" throwing="e"/> 异常通知-->
<aop:around method="around" pointcut-ref="pc1" /> 环绕通知 四合一
</aop:aspect>
</aop:config>
</beans>
这样就可以测试了
import com.qfedu.demo.service.JiSuanQi;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo1 {
/**
* Spring 中,如果被代理的对象有接口,则默认使用 JDK 动态代理
* 如果被代理的对象没有接口,则默认使用 CGLIB 动态代理
*/
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//这里获取到的不是JiSuanQiImpl注入spring容器的对象 而是通aop代理动态生成的JiSuanQi接口的一个实现类的对象
JiSuanQi jiSuanQi = (JiSuanQi) ctx.getBean("jiSuanQi");
jiSuanQi.add(2,2);
jiSuanQi.mine(2,2);
}
}
java配置类进行设置
首先就是依赖和接口与实现类都要有,这里就不重新写了
然后就是java配置类,很简单就是使用注释进行包扫描
package com.qfedu.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.qfedu.demo")
public class JavaConfig {
}
然后就是 切面类
package com.qfedu.demo.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Component
//表示开启aop自动代理功能
//这个注解就类似于在 XML 文件中配置的 aop:config
@EnableAspectJAutoProxy(proxyTargetClass = true)
//表示当前类是一个切面,意味着这个类中既有切点,又有通知
@Aspect
public class LogAdvice {
//定义一个方法表示切点
@Pointcut("execution(* com.qfedu.demo.service.*.*(..))")
public void pc(){
}
//前置通知 名字随意
@Before("pc()")
public void before(JoinPoint jp){
//获取被拦截下来的方法名称
String name = jp.getSignature().getName();
System.out.println(name+"开始执行了");
}
//后置通知
@After("pc()")
public void after(JoinPoint jp){
//获取被拦截下来的方法名称
String name = jp.getSignature().getName();
System.out.println(name+"执行结束了");
}
//异常通知
@AfterThrowing(value = "pc()",throwing = "e")
public void ex(JoinPoint jp,Exception e){
String name = jp.getSignature().getName();
System.out.println(name+"方法抛出了"+e.getMessage()+"异常");
}
//返回值通知
@AfterReturning(value = "pc()",returning = "r")
public void afterReturning(JoinPoint jp,Object r){
String name = jp.getSignature().getName();
System.out.println(name+"方法的返回值是"+r);
}
//环绕通知
@Around("pc()")
public Object around(ProceedingJoinPoint point){
String name = point.getSignature().getName();
try {
//前置通知
System.out.println(name+"方法执行了。。。");
//这个方法就相当于是JDK动态代理中的 Object o = method.invoke(o,objects)
Object proceed = point.proceed();
//后置通知
System.out.println(name+"方法结束了....");
//返回值通知
System.out.println(name+"返回值为"+proceed);
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
//异常通知
System.out.println(name+"方法抛出了"+throwable.getMessage()+"异常");
}
return null;
}
}
这样就写完了,然后就可以测试了
import com.qfedu.demo.config.JavaConfig;
import com.qfedu.demo.service.JiSuanQi;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
JiSuanQi j = (JiSuanQi) ctx.getBean(JiSuanQi.class);
j.add(2,2);
}
}
以上就是spring的AOP的动态代理了
最后再来一个jdbcTemplate的使用方法
先引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qfedu</groupId>
<artifactId>jdbcTempulac</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
</project>
db.properties配置文件
# druid.properties文件的配置
db.driverClassName=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/day09
db.username=root
db.password=123456
# 初始化连接数量
db.initialSize=5
# 最大连接数
db.maxActive=10
# 最大超时时间
db.maxWait=3000
?
xml方式:
? ? ? ? 实体类
package com.qfedu.demo.p1.entity;
public class User {
private Integer uid;
private String uname;
private String upwd;
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", uname='" + uname + '\'' +
", upwd='" + upwd + '\'' +
'}';
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUpwd() {
return upwd;
}
public void setUpwd(String upwd) {
this.upwd = upwd;
}
}
? ? ? ? dao类
package com.qfedu.demo.p1.dao;
import com.qfedu.demo.p1.entity.User;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
public class UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public User getUserByUName(String uname){
User user = jdbcTemplate.queryForObject("select * from user where uname=?", new BeanPropertyRowMapper<>(User.class), uname);
return user;
}
}
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--第一步,引入db.properties配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 第二步 配置数据源-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<!-- 第三步,配置JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="com.qfedu.demo.p1.dao.UserDao" id="userDao">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
最后测试
package com.qfedu.demo.p1;
import com.qfedu.demo.p1.dao.UserDao;
import com.qfedu.demo.p1.entity.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo1 {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = ctx.getBean(UserDao.class);
User user = userDao.getUserByUName("班长");
System.out.println(user);
}
}
下面是java配置类的方法
实体类是一样的?
dao类
package com.qfedu.demo.p2.dao;
import com.qfedu.demo.p2.entity.User;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public User getUserByUName(String uname){
User user = jdbcTemplate.queryForObject("select * from user where uname=?", new BeanPropertyRowMapper<>(User.class), uname);
return user;
}
public List<User> getAllUser(){
return jdbcTemplate.query("select * from user",new BeanPropertyRowMapper<>(User.class));
}
}
java配置类
package com.qfedu.demo.p2.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.qfedu.demo.p2.dao.UserDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
//加载配置文件
@PropertySource("classpath:db.properties")
public class JavaConfig {
//设置值
@Value("${db.url}")
String url;
@Value("${db.username}")
String username;
@Value("${db.password}")
String password;
@Value("${db.driverClassName}")
String driverClassName;
//注册数据源bean
@Bean
DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
ds.setDriverClassName(driverClassName);
return ds;
}
//注册JdbcTemplate的bean
@Bean
JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
//注册UserDao的bean
@Bean
UserDao userDao(){
UserDao userDao = new UserDao();
userDao.setJdbcTemplate(jdbcTemplate());
return userDao;
}
}
测试类
package com.qfedu.demo.p2;
import com.qfedu.demo.p2.config.JavaConfig;
import com.qfedu.demo.p2.dao.UserDao;
import com.qfedu.demo.p2.entity.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
public class Demo2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
UserDao userDao = ctx.getBean(UserDao.class);
List<User> userList = userDao.getAllUser();
for (User user : userList) {
System.out.println(user);
}
}
}
|