AOP面向切面编程
面向对象的编程思想是从上往下,但是面向切面编程的时候就是横向的,思考如下:
创建出对象,里面有a对象的方法,也有b对象的方法,横向抽取两个对象的方法,然后存如到代理对象,就像bean在构造好之后,并没有立即使用,而是经过一步步的增强,BeanDefinitionPostProcessor,BeanPostProcessor,都会增强bean,往里面注入bean原本没有的一些方法
实现service层方法增强
思路:
创建一个类,将增强的方法注入到service的方法,并且返回service的方法:
@Component
public class MyMethodProcessor implements BeanPostProcessor,ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().getPackage().getName().equals("com.liku.service.impl")) {
Object o = Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(Object proxy, Method method, Object[] args) -> {
UserDao bean1 = applicationContext.getBean(UserDao.class);
bean1.before();
bean1.show();
Object invoke = method.invoke(bean, args);
bean1.after();
return invoke;
}
);
return o;
}
return bean;
}
}
实现BeanPostProcessor,在bean初始化之前给它增强并返回,实现ApplicationContextAware接口的set方法,注入applicationContext。以上就实现了方法的增强。
AOP相关概念
目标对象:target,被增强的方法所在的对象
代理对象:proxy,对目标对象进行增强后的对象
连接点:joinPoint,目标对象中可以被增强的方法
切入点:pointCut,目标对象中实际被增强的方法
通知:advice,增强的代码逻辑
切面:aspect,增强和切入点的结合
织入:weaving,将通知和切入点结合动态组合的过程
切点表达式
导入aspectJ依赖:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
execution([访问修饰符]返回类型 包名.类名.方法名(参数))
访问修饰符可以省略不写
返回值类型、某一级包名、类名、方法名可以使用*表示任意
包名与类名之间使用单点,表示该包下的类,使用双点..表示该包及其包下的类
参数列表可以使用两个点..表示任意参数
<aop:config><!--将dao层的方法注入到service层,增强service层方法-->
<!--设置切入点【目标对象service的方法】-->
<aop:pointcut id="pointCut1" expression="execution(public void com.liku.service.impl.UserServiceImpl.show())"/>
<aop:aspect ref="userDao">
<!--method是增强代码类dao拥有的方法-->
<aop:before method="before" pointcut-ref="pointCut1"/>
<aop:after method="after" pointcut-ref="pointCut1"/>
</aop:aspect>
</aop:config>
<!--增强类-->
<bean id="userDao" class="com.liku.dao.impl.UserDaoImpl"></bean>
<bean class="com.liku.service.impl.UserServiceImpl"></bean>
</beans>
aspect
aspectJ有五种类型:
before:在目标方法执行前执行
after-returning:目标方法执行后执行,目标方法异常时,不再执行
around:目标方法执行前后执行,有参数,参数为环绕的目标方法,目标方法异常时,环绕后方法不再执行:
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
Object proceed = joinPoint.proceed();
System.out.println("环绕后");
return proceed;//目标方法的返回值
}
上面三个方法相当于在try里面的内容,有异常的时候不会运行,下面的方法是异常的时候也会执行
after-throwing:目标方法抛出异常的时候执行,需要异常参数throwing
after:不管是否异常,都会执行,相当于finally,最后执行
advisor
通过实现接口的方式来确定通知类型,一般很少用
GClib通过enhancer.setCallBack实现方法的增强
基于注解配置AOP
使用注解配置aop,需要开启aop自动代理,在xml文件中
<!--包扫描组件-->
<context:component-scan base-package="com.liku"/>
<!--开启aop自动代理-->
<aop:aspectj-autoproxy/>
在通知类【增强代码类】上添加对应注解,就可以实现方法增强
@Component
@Aspect
public class UserDaoImpl implements UserDao {
@Pointcut("execution(public void com.liku.service.impl.*.*(..))")
public void pointCut() {
//设置切入点
}
@Before("UserDaoImpl.pointCut()")
public void before() {
System.out.println("before...");
}
@After("UserDaoImpl.pointCut()")
public void after() {
System.out.println("after...");
}
@Around("execution(public void com.liku.service.impl.*.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
Object proceed = joinPoint.proceed();
System.out.println("环绕后");
}
@AfterThrowing(pointcut = "execution(public void com.liku.service.impl.*.*(..))", throwing = "e")
public void throwExce(Exception e) {
System.out.println("异常抛出时运行");
}
}
使用配置类其安全替代xml
@Configuration
@ComponentScan({"com.liku.dao","com.liku.service"})
@EnableAspectJAutoProxy
public class AspectBeanConfig {
}
事务控制
导入spring事务相关坐标:spring-tx的包、配置目标类、使用advisor标签配置切面
转账事务
A转进x钱,B转出钱,有任何一方信息修改失败就回滚事务
思考:
spring集成mybatis,配置文件添加:【spring集成mybatis有写】
<!--扫描注解包-->
<context:component-scan base-package="com.transDemo"/>
<!--配置数据源读取路径-->
<context:property-placeholder location="jdbc.properties"/>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${pwd}"/>
</bean>
<!--配置sql实例化工厂-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--扫描mapper包-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.transDemo.mapper"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
导入spring有关事务的依赖:spring-tx【之前导入spring-jdbc里面含有它,这里就不导入了,官网可搜】
配置事务有关的内容:要注意实体类的形态不是单例模式
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置spring提供好的有关事务的advice-->
<tx:advice id="txAdvisor" transaction-manager="transactionManager">
<tx:attributes>
<!--配置不同的方法事务属性、
name表示方法名称、
isolation表示隔离级别,解决事务并发问题、
timeout超时时间 默认-1
read-only表示只读,不可修改,查询操作设置为true
propagation事务传播行为,解决事务嵌套问题
*表示通配符-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--事务增强的aop-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(void com.transDemo.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvisor" pointcut-ref="txPointCut"/>
</aop:config>
控制事务成功!事务的注解为:@Transactional(属性=值)
注解配置事务
上面有关事务的只留下事务管理器,然后开启事务的自动代理
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务的自动代理-->
<tx:annotation-driven transaction-manager="transactionManager"/>
在类或者方法上加上事务注解:
@Transactional()
public void transMoney(TbAccount inAccount, TbAccount outAccount) {
accountMapper.decMoney(outAccount.getId(), outAccount.getAccount());
int i = 1 / 0;
accountMapper.inreMoney(inAccount.getId(), inAccount.getAccount());
}
用全注解方式配置:
package com.transDemo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@ComponentScan({"com.transDemo.service", "com.transDemo.entity"})
@MapperScan({"com.transDemo.mapper"})
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement
public class TransConfig {
@Bean("dataSource")
public DataSource source(
@Value("${driver}") String dirver,
@Value("${url}") String url,
@Value("${user}") String username,
@Value("${pwd}") String password
) {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(dirver);
druidDataSource.setUsername(username);
druidDataSource.setUrl(url);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean("sqlsessionFactoryBean")
public SqlSessionFactoryBean sessionFactoryBean(
@Qualifier("dataSource") DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean("dataSourceTransactionManager")
public DataSourceTransactionManager dataSourceTransactionManager( @Qualifier("dataSource") DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}