Java之Spring AOP与代理模式
代理模式
面向切片编程(AOP)的底层实现就是代理模式
静态代理
接口
//租房
public interface Rent {
public void rent();
}
真实角色
public class Host implements Rent{
public void rent(){
System.out.println("房东出租房子");
}
}
代理角色
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void fare(){
System.out.println("收中介费");
}
public void seeHouse(){
System.out.println("看房子");
}
public void heTong(){
System.out.println("签合同");
}
public void rent() {
fare();
seeHouse();
heTong();
host.rent();
}
}
客户端访问
public class Client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
简单来说,代理对象继承或实现了真实对象的方法,可以实现与真实对象相同的功能,从而让客户端不直接访问真是对象,而是去访问代理对象,实现原有的操作,让真实类的功能更加纯粹。类似于中介体系。目前来看主要作用在于,当一个业务上线后不能修改原有代码的情况下添加业务功能可能就需要用代理类来实现了。
动态代理
动态代理的角色和静态代理角色一样
动态代理的代理类事动态生成的
动态代理也是通过反射来实现的
动态代理分为两大类:基于接口和基于类
- 基于接口=> JDK原生动态代理
- 基于类 => cglib
- Java字节码实现 => Javassist
这里涉及到两个类 Proxy(代理类)、InvocationHandler接口
InvocationHandler
是代理实例的调用处理程序 实现的接口。
这里其实之前写过动态代理的文章,主要注意的就是动态代理的一个特性:
当调用代理对象接口中的方法时会去调用实现了InvocationHandler的代理处理类的invoke方法。
每个代理实例都具有一个关联的调用处理程序,对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的
invoke
方法。其底层是JVM在运行时产生了代理类的字节码,$proxy0这个字节码文件,通过使用Proxy.newProxyInstance返回得到的动态代理的实例会被处理成$Proxy0,$Proxy0在调用接口的方法时会调用参数中传入的InvocationHandler实现类的invoke方法
示例代码
接口
public interface Rent {
public void rent();
}
真实对象
public class Host implements Rent {
public void rent(){
System.out.println("房东出租房子");
}
}
代理处理程序类
public class ProxyInvocationHandler implements InvocationHandler {
//创建代理对象需要的接口
private Rent rent;
//set方法获取该接口对象
public void setRent(Rent rent) {
this.rent = rent;
}
//创建代理对象
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
//实现InvocationHandler接口的invoke方法,处理动态代理的逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(rent);
return result;
}
}
客户端访问
public class client {
public static void main(String[] args) {
Host host = new Host();
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host);
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
AOP
AOP(Aspect Oriented Programming)面向切面编程,通过预编译的方式和运行期间动态代理实现程序功能统一维护的一种技术。可以理解为一种横向编程思想,不改变原有代码,通过动态代理的机制向切入点(目标方法)织入增强方法来使得整个业务功能得到提升。
Spring实现AOP
Spring中实现AOP的话还是基于动态代理,而之前也提到了动态代理分为三种实现方式,在Spring 中的话默认是使用JDK原生的动态代理去实现AOP,但是当<aop:aspectj-autoproxy proxy-target-class="true"/>
标签中proxy-target-class
的值被定义为true时会使用cglib方式的动态代理实现AOP。
导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8.M1</version>
</dependency>
方式1:Spring API接口实现AOP
主要是利用SpringAPI接口实现,可操作的东西会多一些,如代理类中的参数,方法等
applicationContext.xml
- 配置导入aop约束
- 注册bean
- 设置切入点,标记增强方法在哪个目标类执行
- 设置执行环绕(也就是动态代理中提到的增强方法)
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="serviceImpl" class="com.zh1z3ven.service.ServiceImpl"/>
<bean id="log" class="com.zh1z3ven.log.Log"/>
<bean id="afterLog" class="com.zh1z3ven.log.AfterLog"/>
<!-- 方式1:使用原生的Spring API 接口-->
<!-- 导入aop约束-->
<aop:config>
<!-- 切入点:pointcut, expression:表达式, execution:要执行的位置 com.zh1z3ven.service.ServiceImpl.*(..)代表该类下任意方法任意参数,-->
<aop:pointcut id="pointcut" expression="execution(* com.zh1z3ven.service.ServiceImpl.*(..))"/>
<!-- 执行环绕增加-->
<!-- 将该类定位到id为pointcut的切入点上去-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
这里关于下面的表达式
<aop:pointcut id="pointcut" expression="execution(* com.zh1z3ven.service.ServiceImpl.*(..))"/>
execution(修饰符 返回值 包名.类名/接口名.方法名(参数列表)), (..)可以代表所有参数,()代表一个参数,(,String)代表第一个参数为任何值,第二个参数为String类型。
被代理的接口Service
public interface Service {
public void add();
public void delete();
public void update();
public void select();
}
Service接口实现类ServiceImpl
public class ServiceImpl implements Service{
public void add() {
System.out.println("add success!");
}
public void delete() {
System.out.println("del success!");
}
public void update() {
System.out.println("update success!");
}
public void select() {
System.out.println("select success!");
}
}
增强方法Log
public class Log implements MethodBeforeAdvice {
//此方法会在执行目标对象的方法之前自动去调用
//method:要执行的目标对象的方法
//objects:参数
//o:目标对象
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("执行了" + o.getClass().getName()+"的"+ method.getName());
}
}
增强方法AfterLog
public class AfterLog implements AfterReturningAdvice {
//Object o:方法返回值
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了" + o1.getClass().getName() + "的" + method.getName()+ "方法,返回结果为:" + o);
}
}
结果
执行了com.zh1z3ven.service.ServiceImpl的add
add success!
执行了com.zh1z3ven.service.ServiceImpl的add方法,返回结果为:null
执行了com.zh1z3ven.service.ServiceImpl的delete
del success!
执行了com.zh1z3ven.service.ServiceImpl的delete方法,返回结果为:null
执行了com.zh1z3ven.service.ServiceImpl的update
update success!
执行了com.zh1z3ven.service.ServiceImpl的update方法,返回结果为:null
执行了com.zh1z3ven.service.ServiceImpl的select
select success!
执行了com.zh1z3ven.service.ServiceImpl的select方法,返回结果为:null
方式2:自定义类实现AOP
主要是定义切面,所有实现在自定义类中的方法里,没有第一种方式灵活
applicationContext.xml
<!-- 方式2:自定义类实现AOP-->
<bean id="diy" class="com.zh1z3ven.diy.DiyPointcut"/>
<aop:config>
<!-- 切入面-->
<aop:aspect ref="diy">
<!-- 切入点-->
<aop:pointcut id="point" expression="execution(* com.zh1z3ven.service.ServiceImpl.*(..))"/>
<!-- 通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
方式3:注解实现AOP
- 使用注解@Aspect标注该类为切面
- 编写增强方法
- @Before(expression),@After(expression)设置改方法在那里执行
- 在配置文件注册bean(或注解注册bean),开启注解支持
示例:
applicationContext.xml
<!-- 方式3:注解实现AOP-->
<bean id="anno" class="com.zh1z3ven.diy.Anno"></bean>
<aop:aspectj-autoproxy/>
Anno.java
@Aspect //定义该类为切面
public class Anno {
//定义切入点为com.zh1z3ven.service.ServiceImpl类
@Before("execution(* com.zh1z3ven.service.ServiceImpl.*(..))")
public void before(){
System.out.println("========方法执行前========");
}
}
test
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//这里获得的是Spring动态代理创建的该接口的一个实现类,所以必须强转为service类型
Service service = (Service) context.getBean("serviceImpl");
service.add();
service.delete();
service.update();
service.select();
}
}
结果
========方法执行前========
add success!
========方法执行前========
del success!
========方法执行前========
update success!
========方法执行前========
select success!
Spring整合Mybatis
方式1:sqlSessionTemplate
mybatis-config.xml,这里配置了log4j,注意需要有个Log4j.properties的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
</configuration>
编写dao接口实现类Impl和mapper.xml
UserMapper.xml,老一套Mybatis的select语句,没啥说的
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zh1z3ven.mapper.UserMapper">
<select id="getUserList" resultType="com.zh1z3ven.pojo.User">
select * from user;
</select>
</mapper>
UserMapperImpl.java,实现类,注意需要将sqlSession提出来,并给出set方法才能在bean注册时对其注入属性值。查询方法也是老一套mybatis获取sqlSession后的操作了,没啥可解释的。这里其实相当于之前在utils里写的获取sqlSession的操作全部放到了spring配置文件中,通过spring去完成的获取sqlSession。
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> getUserList() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
return userList;
}
}
spring-mybatis.xml
-
创建数据源DataSource,也就是之前的JDBC连接
<!-- DataSource:使用Spring提供的数据源DataSource替换Mybatis去操作数据库, 相当于Spring自己提供了一个JDBC--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>
-
sqlSessionFactory,创建sqlSession工厂,绑定mybatis配置文件和Mapper.xml文件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/zh1z3ven/mapper/*.xml"/> </bean>
-
创建sqlSessionTemplate,也就是之前的sqlSession对象,只能用构造器注入
<!-- sqlSessionTemplate相当于sqlSession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!-- 只能使用构造器注入,因为没有set方法--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
-
注册实现类bean,将在spring中获取到的sqlSessionTemplate对象注入到实现类内
<bean id="userMapperImpl" class="com.zh1z3ven.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>
测试
static Logger logger = Logger.getLogger(Mytest.class);
@Test
public void getUserList2(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-mybatis.xml");
UserMapperImpl userMapperImpl = context.getBean("userMapperImpl", UserMapperImpl.class);
for (User user : userMapperImpl.getUserList()) {
logger.info("info: " + user);
}
}
//结果
/*
[org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[org.mybatis.spring.SqlSessionUtils]-Creating a new SqlSession
[org.mybatis.spring.SqlSessionUtils]-SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@367ffa75] was not registered for synchronization because synchronization is not active
[org.mybatis.spring.transaction.SpringManagedTransaction]-JDBC Connection [com.mysql.jdbc.JDBC4Connection@366647c2] will not be managed by Spring
[com.zh1z3ven.mapper.UserMapper.getUserList]-==> Preparing: select * from user;
[com.zh1z3ven.mapper.UserMapper.getUserList]-==> Parameters:
[com.zh1z3ven.mapper.UserMapper.getUserList]-<== Total: 5
[org.mybatis.spring.SqlSessionUtils]-Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@367ffa75]
[Mytest]-info: User(id=1, name=zhangsan, pwd=123456)
[Mytest]-info: User(id=2, name=lisi, pwd=123456)
[Mytest]-info: User(id=3, name=wangwu, pwd=123456)
[Mytest]-info: User(id=4, name=liwu, pwd=123321)
[Mytest]-info: User(id=6, name=zh1z3ven, pwd=12344321)
*/
方式2:sqlSessionDaoSupport
这里主要变动在于需要实现类继承SqlSessionDaoSupport类,通过getSqlSession()即可活动sqlSession对象;同时在spring-mybatis.xml中需要注册<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
UserMapperImpl2.java
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
public List<User> getUserList() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
return userList;
}
}
注册bean
<bean id="userMapperImpl2" class="com.zh1z3ven.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
测试
Test.java
@Test
public void getUserList2(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-mybatis.xml");
UserMapper userMapper = context.getBean("userMapperImpl2", UserMapper.class);
for (User user : userMapper.getUserList()) {
logger.info("info: " + user);
}
}
//结果
/*
[org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[org.mybatis.spring.SqlSessionUtils]-Creating a new SqlSession
[org.mybatis.spring.SqlSessionUtils]-SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@24269709] was not registered for synchronization because synchronization is not active
[org.mybatis.spring.transaction.SpringManagedTransaction]-JDBC Connection [com.mysql.jdbc.JDBC4Connection@2abf4075] will not be managed by Spring
[com.zh1z3ven.mapper.UserMapper.getUserList]-==> Preparing: select * from user;
[com.zh1z3ven.mapper.UserMapper.getUserList]-==> Parameters:
[com.zh1z3ven.mapper.UserMapper.getUserList]-<== Total: 5
[org.mybatis.spring.SqlSessionUtils]-Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@24269709]
[Mytest]-info: User(id=1, name=zhangsan, pwd=123456)
[Mytest]-info: User(id=2, name=lisi, pwd=123456)
[Mytest]-info: User(id=3, name=wangwu, pwd=123456)
[Mytest]-info: User(id=4, name=liwu, pwd=123321)
[Mytest]-info: User(id=6, name=zh1z3ven, pwd=12344321)
*/